diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 82746d98c..a67218abd 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -183,8 +184,9 @@ + android:showWhenLocked="true" + android:resizeableActivity="true" + android:supportsPictureInPicture="true"/> @@ -252,10 +254,6 @@ - - @@ -265,12 +263,6 @@ - - - - - - mIncomingReceivedActivity = CallIncomingActivity.class; @@ -215,8 +213,6 @@ public final class LinphoneService extends Service { ContactsContract.Contacts.CONTENT_URI, true, mContactsManager); } - mBluetoothManager = new BluetoothManager(); - Compatibility.createChatShortcuts(this); mOrientationHelper.enable(); @@ -268,7 +264,6 @@ public final class LinphoneService extends Service { mNotificationManager.destroy(); } mContactsManager.destroy(); - mBluetoothManager.destroy(); if (LinphonePreferences.instance().useJavaLogger()) { Factory.instance().getLoggingService().removeListener(mJavaLoggingService); @@ -311,10 +306,6 @@ public final class LinphoneService extends Service { return mContactsManager; } - public BluetoothManager getBluetoothManager() { - return mBluetoothManager; - } - public void createOverlay() { if (mOverlay != null) destroyOverlay(); diff --git a/app/src/main/java/org/linphone/activities/AboutActivity.java b/app/src/main/java/org/linphone/activities/AboutActivity.java index 475d64760..0b1c440f8 100644 --- a/app/src/main/java/org/linphone/activities/AboutActivity.java +++ b/app/src/main/java/org/linphone/activities/AboutActivity.java @@ -48,6 +48,10 @@ public class AboutActivity extends MainActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } + mOnBackPressGoHome = false; mAlwaysHideTabBar = true; diff --git a/app/src/main/java/org/linphone/activities/DialerActivity.java b/app/src/main/java/org/linphone/activities/DialerActivity.java index e20537925..e121c348c 100644 --- a/app/src/main/java/org/linphone/activities/DialerActivity.java +++ b/app/src/main/java/org/linphone/activities/DialerActivity.java @@ -56,6 +56,9 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } // Uses the fragment container layout to inflate the dialer view instead of using a fragment View dialerView = LayoutInflater.from(this).inflate(R.layout.dialer, null, false); diff --git a/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java b/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java index 055741b8a..5bfacebf2 100644 --- a/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java +++ b/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java @@ -22,14 +22,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import android.os.Bundle; import org.linphone.LinphoneService; -public abstract class LinphoneGenericActivity extends ThemableActivity { +public abstract class LinphoneGenericActivity extends ThemeableActivity { + protected boolean mAbortCreation; + @Override protected void onCreate(Bundle savedInstanceState) { + mAbortCreation = false; super.onCreate(savedInstanceState); // After a crash, Android restart the last Activity so we need to check // if all dependencies are loaded if (!LinphoneService.isReady()) { startActivity(getIntent().setClass(this, LinphoneLauncherActivity.class)); + mAbortCreation = true; finish(); } } diff --git a/app/src/main/java/org/linphone/activities/MainActivity.java b/app/src/main/java/org/linphone/activities/MainActivity.java index 4cfdbd973..12639d3a9 100644 --- a/app/src/main/java/org/linphone/activities/MainActivity.java +++ b/app/src/main/java/org/linphone/activities/MainActivity.java @@ -63,7 +63,7 @@ import org.linphone.core.ProxyConfig; import org.linphone.core.RegistrationState; import org.linphone.core.tools.Log; import org.linphone.fragments.EmptyFragment; -import org.linphone.fragments.StatusFragment; +import org.linphone.fragments.StatusBarFragment; import org.linphone.history.HistoryActivity; import org.linphone.menu.SideMenuFragment; import org.linphone.settings.LinphonePreferences; @@ -73,7 +73,7 @@ import org.linphone.utils.LinphoneUtils; import org.linphone.utils.PushNotificationUtils; public abstract class MainActivity extends LinphoneGenericActivity - implements StatusFragment.MenuClikedListener, SideMenuFragment.QuitClikedListener { + implements StatusBarFragment.MenuClikedListener, SideMenuFragment.QuitClikedListener { private static final int MAIN_PERMISSIONS = 1; private static final int FRAGMENT_SPECIFIC_PERMISSION = 2; @@ -88,7 +88,7 @@ public abstract class MainActivity extends LinphoneGenericActivity private LinearLayout mTabBar; private SideMenuFragment mSideMenuFragment; - private StatusFragment mStatusFragment; + private StatusBarFragment mStatusBarFragment; protected boolean mOnBackPressGoHome; protected boolean mAlwaysHideTabBar; @@ -99,6 +99,9 @@ public abstract class MainActivity extends LinphoneGenericActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } if (!LinphoneService.isReady()) { finish(); @@ -171,8 +174,8 @@ public abstract class MainActivity extends LinphoneGenericActivity } }); - mStatusFragment = - (StatusFragment) getFragmentManager().findFragmentById(R.id.status_fragment); + mStatusBarFragment = + (StatusBarFragment) getFragmentManager().findFragmentById(R.id.status_fragment); DrawerLayout mSideMenu = findViewById(R.id.side_menu); RelativeLayout mSideMenuContent = findViewById(R.id.side_menu_content); @@ -316,7 +319,7 @@ public abstract class MainActivity extends LinphoneGenericActivity mDialerSelected.setVisibility(View.GONE); mChatSelected.setVisibility(View.GONE); - mStatusFragment.setMenuListener(this); + mStatusBarFragment.setMenuListener(this); mSideMenuFragment.setQuitListener(this); mSideMenuFragment.displayAccountsInSideMenu(); @@ -334,7 +337,7 @@ public abstract class MainActivity extends LinphoneGenericActivity @Override protected void onPause() { - mStatusFragment.setMenuListener(null); + mStatusBarFragment.setMenuListener(null); mSideMenuFragment.setQuitListener(null); Core core = LinphoneManager.getCore(); diff --git a/app/src/main/java/org/linphone/activities/ThemableActivity.java b/app/src/main/java/org/linphone/activities/ThemeableActivity.java similarity index 73% rename from app/src/main/java/org/linphone/activities/ThemableActivity.java rename to app/src/main/java/org/linphone/activities/ThemeableActivity.java index 0494e4561..31dde8c8f 100644 --- a/app/src/main/java/org/linphone/activities/ThemableActivity.java +++ b/app/src/main/java/org/linphone/activities/ThemeableActivity.java @@ -1,7 +1,7 @@ package org.linphone.activities; /* -ThemableActivity.java +ThemeableActivity.java Copyright (C) 2019 Belledonne Communications, Grenoble, France This program is free software; you can redistribute it and/or @@ -23,9 +23,10 @@ import android.content.pm.ActivityInfo; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import org.linphone.R; +import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; -public abstract class ThemableActivity extends AppCompatActivity { +public abstract class ThemeableActivity extends AppCompatActivity { private int mTheme; @Override @@ -43,16 +44,30 @@ public abstract class ThemableActivity extends AppCompatActivity { super.onCreate(savedInstanceState); } + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putInt("Theme", mTheme); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + mTheme = savedInstanceState.getInt("Theme"); + super.onRestoreInstanceState(savedInstanceState); + } + @Override protected void onResume() { super.onResume(); if (LinphonePreferences.instance().isDarkModeEnabled()) { if (mTheme != R.style.LinphoneStyleDark) { + Log.w("[Themeable Activity] Recreate Activity cause theme doesn't match"); recreate(); } } else { if (mTheme != R.style.LinphoneStyleLight) { + Log.w("[Themeable Activity] Recreate Activity cause theme doesn't match"); recreate(); } } diff --git a/app/src/main/java/org/linphone/assistant/AssistantActivity.java b/app/src/main/java/org/linphone/assistant/AssistantActivity.java index 61e9f4bb0..e4a526e7e 100644 --- a/app/src/main/java/org/linphone/assistant/AssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/AssistantActivity.java @@ -30,7 +30,7 @@ import android.widget.ImageView; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.activities.DialerActivity; -import org.linphone.activities.ThemableActivity; +import org.linphone.activities.ThemeableActivity; import org.linphone.core.AccountCreator; import org.linphone.core.Core; import org.linphone.core.DialPlan; @@ -39,7 +39,7 @@ import org.linphone.core.ProxyConfig; import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; -public abstract class AssistantActivity extends ThemableActivity +public abstract class AssistantActivity extends ThemeableActivity implements CountryPicker.CountryPickedListener { static AccountCreator mAccountCreator; diff --git a/app/src/main/java/org/linphone/assistant/CountryAdapter.java b/app/src/main/java/org/linphone/assistant/CountryAdapter.java index c5f24a57e..fa84947c2 100644 --- a/app/src/main/java/org/linphone/assistant/CountryAdapter.java +++ b/app/src/main/java/org/linphone/assistant/CountryAdapter.java @@ -69,7 +69,7 @@ class CountryAdapter extends BaseAdapter implements Filterable { if (convertView != null) { view = convertView; } else { - view = mInflater.inflate(R.layout.country_cell, parent, false); + view = mInflater.inflate(R.layout.assistant_country_cell, parent, false); } DialPlan c = mFilteredCountries.get(position); diff --git a/app/src/main/java/org/linphone/call/CallActivity.java b/app/src/main/java/org/linphone/call/CallActivity.java index 45d25ee21..5ff46a115 100644 --- a/app/src/main/java/org/linphone/call/CallActivity.java +++ b/app/src/main/java/org/linphone/call/CallActivity.java @@ -21,29 +21,23 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import android.Manifest; import android.app.Dialog; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; 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.Handler; import android.os.SystemClock; -import android.text.Html; -import android.view.Gravity; +import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.TextureView; import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.widget.AdapterView; import android.widget.Button; import android.widget.Chronometer; import android.widget.ImageView; @@ -51,16 +45,10 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.drawerlayout.widget.DrawerLayout; -import java.text.DecimalFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.activities.DialerActivity; @@ -68,1091 +56,768 @@ 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.core.Address; -import org.linphone.core.AddressFamily; import org.linphone.core.Call; -import org.linphone.core.Call.State; -import org.linphone.core.CallListenerStub; -import org.linphone.core.CallParams; -import org.linphone.core.CallStats; 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.MediaEncryption; -import org.linphone.core.PayloadType; -import org.linphone.core.Player; -import org.linphone.core.StreamType; +import org.linphone.core.VideoDefinition; import org.linphone.core.tools.Log; -import org.linphone.fragments.StatusFragment; -import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration; -import org.linphone.receivers.BluetoothManager; import org.linphone.settings.LinphonePreferences; +import org.linphone.utils.AndroidAudioManager; import org.linphone.utils.LinphoneUtils; import org.linphone.views.ContactAvatar; import org.linphone.views.Numpad; public class CallActivity extends LinphoneGenericActivity - implements OnClickListener, ActivityCompat.OnRequestPermissionsResultCallback { + 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 PERMISSIONS_REQUEST_CAMERA = 202; - private static final int PERMISSIONS_ENABLED_CAMERA = 203; - private static final int PERMISSIONS_ENABLED_MIC = 204; - private static final int PERMISSIONS_EXTERNAL_STORAGE = 205; - private static long sTimeRemind = 0; - private Handler mControlsHandler = new Handler(); - private Runnable mControls; - private ImageView mSwitchCamera; - private TextView mMissedChats; - private RelativeLayout mActiveCallHeader, mSideMenuContent, mAvatarLayout; - private ImageView mPause; - private ImageView mDialer; - private ImageView mVideo; - private ImageView mMicro; - private ImageView mSpeaker; - private ImageView mOptions; - private ImageView mAddCall; - private ImageView mTransfer; - private ImageView mConference; - private ImageView mConferenceStatus; - private ImageView mRecordCall; - private ImageView mRecording; - private ImageView mAudioRoute; - private ImageView mRouteSpeaker; - private ImageView mRouteEarpiece; - private ImageView mRouteBluetooth; - private LinearLayout mNoCurrentCall, mCallInfo, mCallPaused; - private ProgressBar mVideoProgress; - private StatusFragment mStatus; - private CallAudioFragment mAudioCallFragment; - private CallVideoFragment mVideoCallFragment; - private boolean mIsSpeakerEnabled = false, - mIsMicMuted = false, - mIsTransferAllowed, - mIsVideoAsk, - mIsRecording = false; - private LinearLayout mControlsLayout; + 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 Handler mHandler = new Handler(); + private Runnable mHideControlsRunnable = + new Runnable() { + @Override + public void run() { + // Make sure that at the time this is executed this is still required + Call call = mCore.getCurrentCall(); + if (call != null && call.getCurrentParams().videoEnabled()) { + updateButtonsVisibility(false); + } + } + }; + + private int 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 Numpad mNumpad; - private int mCameraNumber; - private CountDownTimer mCountDownTimer; - private boolean mIsVideoCallPaused = false; - private Dialog mDialog = null; - private HeadsetReceiver mHeadsetReceiver; + private TextView mContactName, mMissedMessages; + private ProgressBar mVideoInviteInProgress; + private Chronometer mCallTimer; + private CountDownTimer mCallUpdateCountDownTimer; + private Dialog mCallUpdateDialog; - private LinearLayout mCallsList, mConferenceList; - private LayoutInflater mInflater; - private ViewGroup mContainer; - private boolean mIsConferenceRunning = false; - private CoreListenerStub mListener; - private DrawerLayout mSideMenu; - - private final Handler mHandler = new Handler(); - private Timer mTimer; - private TimerTask mTask; - private HashMap mEncoderTexts; - private HashMap mDecoderTexts; - private Call mCallDisplayedInStats; - - private boolean mOldIsSpeakerEnabled = false; - - private CallActivityInterface mCallInterface; + private CallStatsFragment mStatsFragment; + private Core mCore; + private CoreListener mListener; + private AndroidAudioManager mAudioManager; + private VideoZoomHelper mZoomHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Compatibility.setShowWhenLocked(this, true); setContentView(R.layout.call); - // Earset Connectivity Broadcast Processing - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction("android.intent.action.HEADSET_PLUG"); - mHeadsetReceiver = new HeadsetReceiver(); - registerReceiver(mHeadsetReceiver, intentFilter); + mLocalPreview = findViewById(R.id.local_preview_texture); + mLocalPreview.setOnTouchListener( + new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + moveLocalPreview(event); + return true; + } + }); - mIsTransferAllowed = - getApplicationContext().getResources().getBoolean(R.bool.allow_transfers); + mRemoteVideo = findViewById(R.id.remote_video_texture); + mRemoteVideo.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + makeButtonsVisibleTemporary(); + } + }); - mCameraNumber = AndroidCameraConfiguration.retrieveCameras().length; + 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); - mEncoderTexts = new HashMap<>(); - mDecoderTexts = new HashMap<>(); + 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(); + } + } + }); + + mNumpad = findViewById(R.id.numpad); + + ImageView numpadButton = findViewById(R.id.dialer); + numpadButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + mNumpad.setVisibility( + mNumpad.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + } + }); + + 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.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) { - displayMissedChats(); + updateMissedChatCount(); } @Override public void onCallStateChanged( - Core core, final Call call, Call.State state, String message) { - if (core.getCallsNb() == 0) { - finish(); - return; - } - - if (state == State.IncomingReceived || state == State.IncomingEarlyMedia) { - // This scenario will be handled by the Service listener - return; - } else if (state == State.Paused - || state == State.PausedByRemote - || state == State.Pausing) { + Core core, Call call, Call.State state, String message) { + if (state == Call.State.End || state == Call.State.Released) { + if (core.getCallsNb() == 0) { + finish(); + } + } else if (state == Call.State.PausedByRemote) { if (core.getCurrentCall() != null) { - mVideo.setEnabled(false); - } - if (isVideoEnabled(call)) { - showAudioView(); - } - } else if (state == State.Resuming) { - if (LinphonePreferences.instance().isVideoEnabled()) { - mStatus.refreshStatusItems(call); - if (call.getCurrentParams().videoEnabled()) { - showVideoView(); - } + showVideoControls(false); + mCallPausedByRemote.setVisibility(View.VISIBLE); } + } else if (state == Call.State.Pausing || state == Call.State.Paused) { if (core.getCurrentCall() != null) { - mVideo.setEnabled(true); + showVideoControls(false); } - } else if (state == State.StreamsRunning) { - switchVideo(isVideoEnabled(call)); - enableAndRefreshInCallActions(); + } else if (state == Call.State.StreamsRunning) { + mCallPausedByRemote.setVisibility(View.GONE); - if (mStatus != null) { - mVideoProgress.setVisibility(View.GONE); - mStatus.refreshStatusItems(call); - } - } else if (state == State.UpdatedByRemote) { - // If the correspondent proposes video while audio call + 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 remoteVideo = call.getRemoteParams().videoEnabled(); - boolean localVideo = call.getCurrentParams().videoEnabled(); - boolean autoAcceptCameraPolicy = - LinphonePreferences.instance() - .shouldAutomaticallyAcceptVideoRequests(); - if (remoteVideo - && !localVideo - && !autoAcceptCameraPolicy - && !core.isInConference()) { + boolean showAcceptUpdateDialog = + LinphoneManager.getCallManager() + .shouldShowAcceptCallUpdateDialog(call); + if (showAcceptUpdateDialog) { showAcceptCallUpdateDialog(); createTimerForDialog(SECONDS_BEFORE_DENYING_CALL_UPDATE); } } - refreshIncallUi(); - mTransfer.setEnabled(core.getCurrentCall() != null); - } - - @Override - public void onCallEncryptionChanged( - Core core, - final Call call, - boolean encrypted, - String authenticationToken) { - if (mStatus != null) { - if (call.getCurrentParams() - .getMediaEncryption() - .equals(MediaEncryption.ZRTP) - && !call.getAuthenticationTokenVerified()) { - mStatus.showZRTPDialog(call); - } - mStatus.refreshStatusItems(call); - } + updateButtons(); + updateCallsList(); } }; - - if (findViewById(R.id.fragmentContainer) != null) { - initUI(); - - Core core = LinphoneManager.getCore(); - if (core.getCallsNb() > 0) { - Call call = core.getCalls()[0]; - - if (LinphoneUtils.isCallEstablished(call)) { - enableAndRefreshInCallActions(); - } - } - if (savedInstanceState != null) { - // Fragment already created, no need to create it again (else it will generate a - // memory leak with duplicated fragments) - mIsSpeakerEnabled = savedInstanceState.getBoolean("Speaker"); - mIsMicMuted = savedInstanceState.getBoolean("Mic"); - mIsVideoCallPaused = savedInstanceState.getBoolean("VideoCallPaused"); - if (savedInstanceState.getBoolean("AskingVideo")) { - showAcceptCallUpdateDialog(); - sTimeRemind = savedInstanceState.getLong("sTimeRemind"); - createTimerForDialog(sTimeRemind); - } - refreshInCallActions(); - return; - } else { - mIsSpeakerEnabled = LinphoneManager.getAudioManager().isAudioRoutedToSpeaker(); - mIsMicMuted = !core.micEnabled(); - } - - Fragment callFragment; - if (isVideoEnabled(core.getCurrentCall())) { - callFragment = new CallVideoFragment(); - mVideoCallFragment = (CallVideoFragment) callFragment; - displayVideoCall(false); - LinphoneManager.getAudioManager().routeAudioToSpeaker(); - mIsSpeakerEnabled = true; - } else { - callFragment = new CallAudioFragment(); - mAudioCallFragment = (CallAudioFragment) callFragment; - } - - if (BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - BluetoothManager.getInstance().routeAudioToBluetooth(); - } - - callFragment.setArguments(getIntent().getExtras()); - getFragmentManager() - .beginTransaction() - .add(R.id.fragmentContainer, callFragment) - .commitAllowingStateLoss(); - } - - mCallInterface = - new CallActivityInterface() { - @Override - public void setSpeakerEnabled(boolean enable) { - CallActivity.this.setSpeakerEnabled(enable); - } - - @Override - public void refreshInCallActions() { - CallActivity.this.refreshInCallActions(); - } - - @Override - public void resetCallControlsHidingTimer() { - CallActivity.this.resetCallControlsHidingTimer(); - } - }; - } - - private void createTimerForDialog(long time) { - mCountDownTimer = - new CountDownTimer(time, 1000) { - public void onTick(long millisUntilFinished) { - sTimeRemind = millisUntilFinished; - } - - public void onFinish() { - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - acceptCallUpdate(false); - } - }.start(); - } - - private boolean isVideoEnabled(Call call) { - if (call != null) { - return call.getCurrentParams().videoEnabled(); - } - return false; } @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean("Speaker", LinphoneManager.getAudioManager().isAudioRoutedToSpeaker()); - outState.putBoolean("Mic", !LinphoneManager.getCore().micEnabled()); - outState.putBoolean("VideoCallPaused", mIsVideoCallPaused); - outState.putBoolean("AskingVideo", mIsVideoAsk); - outState.putLong("sTimeRemind", sTimeRemind); - if (mDialog != null) mDialog.dismiss(); + protected void onStart() { + super.onStart(); + + mCore = LinphoneManager.getCore(); + if (mCore != null) { + mCore.setNativeVideoWindowId(mRemoteVideo); + mCore.setNativePreviewWindowId(mLocalPreview); + mCore.addListener(mListener); + } } - private boolean isTablet() { - return getResources().getBoolean(R.bool.isTablet); + @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(); + } } - private void initUI() { - mInflater = LayoutInflater.from(this); - mContainer = findViewById(R.id.topLayout); - mCallsList = findViewById(R.id.calls_list); - mConferenceList = findViewById(R.id.conference_list); + @Override + protected void onPause() { + super.onPause(); - // TopBar - mVideo = findViewById(R.id.video); - mVideo.setOnClickListener(this); - mVideo.setEnabled(false); + ContactsManager.getInstance().removeContactsListener(this); + LinphoneManager.getCallManager().setCallInterface(null); + } - mVideoProgress = findViewById(R.id.video_in_progress); - mVideoProgress.setVisibility(View.GONE); + @Override + protected void onDestroy() { + super.onDestroy(); - mMicro = findViewById(R.id.micro); - mMicro.setOnClickListener(this); - - mSpeaker = findViewById(R.id.speaker); - mSpeaker.setOnClickListener(this); - - mOptions = findViewById(R.id.options); - mOptions.setOnClickListener(this); - mOptions.setEnabled(false); - - // BottonBar - ImageView mHangUp = findViewById(R.id.hang_up); - mHangUp.setOnClickListener(this); - - mDialer = findViewById(R.id.dialer); - mDialer.setOnClickListener(this); - - mNumpad = findViewById(R.id.numpad); - mNumpad.getBackground().setAlpha(240); - - ImageView mChat = findViewById(R.id.chat); - mChat.setOnClickListener(this); - mMissedChats = findViewById(R.id.missed_chats); - - // Others - - // Active Call - mCallInfo = findViewById(R.id.active_call_info); - - mPause = findViewById(R.id.pause); - mPause.setOnClickListener(this); - mPause.setEnabled(false); - - mActiveCallHeader = findViewById(R.id.active_call); - mNoCurrentCall = findViewById(R.id.no_current_call); - mCallPaused = findViewById(R.id.remote_pause); - - mAvatarLayout = findViewById(R.id.avatar_layout); - - // Options - mAddCall = findViewById(R.id.add_call); - mAddCall.setOnClickListener(this); - mAddCall.setEnabled(false); - - mTransfer = findViewById(R.id.transfer); - mTransfer.setOnClickListener(this); - mTransfer.setEnabled(false); - - mConference = findViewById(R.id.conference); - mConference.setEnabled(false); - mConference.setOnClickListener(this); - - mRecordCall = findViewById(R.id.record_call); - mRecordCall.setOnClickListener(this); - mRecordCall.setEnabled(false); - - mRecording = findViewById(R.id.recording); - mRecording.setOnClickListener(this); - mRecording.setEnabled(false); - mRecording.setVisibility(View.GONE); - - try { - mAudioRoute = findViewById(R.id.audio_route); - mAudioRoute.setOnClickListener(this); - mRouteSpeaker = findViewById(R.id.route_speaker); - mRouteSpeaker.setOnClickListener(this); - mRouteEarpiece = findViewById(R.id.route_earpiece); - mRouteEarpiece.setOnClickListener(this); - mRouteBluetooth = findViewById(R.id.route_bluetooth); - mRouteBluetooth.setOnClickListener(this); - } catch (NullPointerException npe) { - Log.e("Bluetooth: Audio routes mMenu disabled on tablets for now (1)"); + Core core = LinphoneManager.getCore(); + if (core != null) { + core.removeListener(mListener); + core.setNativeVideoWindowId(null); + core.setNativePreviewWindowId(null); } - - mSwitchCamera = findViewById(R.id.switchCamera); - mSwitchCamera.setOnClickListener(this); - - mControlsLayout = findViewById(R.id.menu); - - if (!mIsTransferAllowed) { - mAddCall.setBackgroundResource(R.drawable.options_add_call); + if (mZoomHelper != null) { + mZoomHelper.destroy(); } + } - if (BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - try { - mAudioRoute.setVisibility(View.VISIBLE); - mSpeaker.setVisibility(View.GONE); - } catch (NullPointerException npe) { - Log.e("Bluetooth: Audio routes mMenu disabled on tablets for now (2)"); - } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + } + + @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 { - try { - mAudioRoute.setVisibility(View.GONE); - mSpeaker.setVisibility(View.VISIBLE); - } catch (NullPointerException npe) { - Log.e("Bluetooth: Audio routes mMenu disabled on tablets for now (3)"); - } - } - - createInCallStats(); - LinphoneManager.getInstance().changeStatusToOnThePhone(); - } - - private void checkAndRequestPermission(String permission, int result) { - int permissionGranted = getPackageManager().checkPermission(permission, getPackageName()); - Log.i( - "[Permission] " - + permission - + " is " - + (permissionGranted == PackageManager.PERMISSION_GRANTED - ? "granted" - : "denied")); - - if (permissionGranted != PackageManager.PERMISSION_GRANTED) { - Log.i("[Permission] Asking for " + permission); - ActivityCompat.requestPermissions(this, new String[] {permission}, result); + mStatsFragment.openOrCloseSideMenu(true, true); } } @Override public void onRequestPermissionsResult( - int requestCode, String[] permissions, final int[] grantResults) { - for (int i = 0; i < permissions.length; i++) { - Log.i( - "[Permission] " - + permissions[i] - + " is " - + (grantResults[i] == PackageManager.PERMISSION_GRANTED - ? "granted" - : "denied")); - } + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + // Permission not granted, won't change anything + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return; switch (requestCode) { - case PERMISSIONS_REQUEST_CAMERA: - LinphoneUtils.dispatchOnUIThread( - new Runnable() { - @Override - public void run() { - acceptCallUpdate( - grantResults[0] == PackageManager.PERMISSION_GRANTED); - } - }); + case CAMERA_TO_TOGGLE_VIDEO: + toggleVideo(); break; - case PERMISSIONS_ENABLED_CAMERA: - LinphoneUtils.dispatchOnUIThread( - new Runnable() { - @Override - public void run() { - disableVideo(grantResults[0] != PackageManager.PERMISSION_GRANTED); - } - }); + case MIC_TO_DISABLE_MUTE: + toggleMic(); break; - case PERMISSIONS_ENABLED_MIC: - LinphoneUtils.dispatchOnUIThread( - new Runnable() { - @Override - public void run() { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - toggleMicro(); - } - } - }); + case WRITE_EXTERNAL_STORAGE_FOR_RECORDING: + toggleRecording(); + break; + case CAMERA_TO_ACCEPT_UPDATE: + acceptCallUpdate(true); break; - case PERMISSIONS_EXTERNAL_STORAGE: - LinphoneUtils.dispatchOnUIThread( - new Runnable() { - @Override - public void run() { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - toggleCallRecording(!mIsRecording); - } - } - }); } } - private void createInCallStats() { - mSideMenu = findViewById(R.id.side_menu); - ImageView mMenu = findViewById(R.id.call_quality); - - mSideMenuContent = findViewById(R.id.side_menu_content); - - mMenu.setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View view) { - if (mSideMenu.isDrawerVisible(Gravity.LEFT)) { - mSideMenu.closeDrawer(mSideMenuContent); - } else { - mSideMenu.openDrawer(mSideMenuContent); - } - } - }); - - Core core = LinphoneManager.getCore(); - if (core != null) { - initCallStatsRefresher(core.getCurrentCall(), findViewById(R.id.incall_stats)); - } + 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 void refreshIncallUi() { - refreshInCallActions(); - refreshCallList(); - enableAndRefreshInCallActions(); - displayMissedChats(); - } - - private void setSpeakerEnabled(boolean enabled) { - mIsSpeakerEnabled = enabled; - } - - private void refreshInCallActions() { - if (!LinphonePreferences.instance().isVideoEnabled() || mIsConferenceRunning) { - mVideo.setEnabled(false); - } else { - if (mVideo.isEnabled()) { - if (isVideoEnabled(LinphoneManager.getCore().getCurrentCall())) { - mVideo.setSelected(true); - mVideoProgress.setVisibility(View.INVISIBLE); - } else { - mVideo.setSelected(false); - } - } else { - mVideo.setSelected(false); - } + 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; } - if (getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()) - != PackageManager.PERMISSION_GRANTED) { - mVideo.setSelected(false); - } - - mSpeaker.setSelected(mIsSpeakerEnabled); - - if (getPackageManager().checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName()) - != PackageManager.PERMISSION_GRANTED) { - mIsMicMuted = true; - } - mMicro.setSelected(mIsMicMuted); - - try { - mRouteSpeaker.setSelected(false); - if (BluetoothManager.getInstance().isUsingBluetoothAudioRoute()) { - mIsSpeakerEnabled = false; // We need this if mIsSpeakerEnabled wasn't set correctly - mRouteEarpiece.setSelected(false); - mRouteBluetooth.setSelected(true); - return; - } else { - mRouteEarpiece.setSelected(true); - mRouteBluetooth.setSelected(false); - } - - if (mIsSpeakerEnabled) { - mRouteSpeaker.setSelected(true); - mRouteEarpiece.setSelected(false); - mRouteBluetooth.setSelected(false); - } - } catch (NullPointerException npe) { - Log.e("Bluetooth: Audio routes mMenu disabled on tablets for now (4)"); - } - } - - private void enableAndRefreshInCallActions() { - int confsize = 0; - - Core core = LinphoneManager.getCore(); - if (core.isInConference()) { - confsize = core.getConferenceSize() - (core.getConference() != null ? 1 : 0); - } - - // Enabled mTransfer button - mTransfer.setEnabled(mIsTransferAllowed && !core.soundResourcesLocked()); - - // Enable mConference button - mConference.setEnabled( - core.getCallsNb() > 1 - && core.getCallsNb() > confsize - && !core.soundResourcesLocked()); - - mAddCall.setEnabled(core.getCallsNb() < core.getMaxCalls() && !core.soundResourcesLocked()); - mOptions.setEnabled( - !getResources().getBoolean(R.bool.disable_options_in_call) - && (mAddCall.isEnabled() || mTransfer.isEnabled())); - - Call currentCall = core.getCurrentCall(); - - mRecordCall.setEnabled( - !core.soundResourcesLocked() - && currentCall != null - && currentCall.getCurrentParams().getRecordFile() != null); - mRecordCall.setSelected(mIsRecording); - - mRecording.setEnabled(mIsRecording); - mRecording.setVisibility(mIsRecording ? View.VISIBLE : View.GONE); - - mVideo.setEnabled( - currentCall != null - && LinphonePreferences.instance().isVideoEnabled() - && !currentCall.mediaInProgress()); - - mPause.setEnabled(currentCall != null && !currentCall.mediaInProgress()); - - mMicro.setEnabled(true); - mSpeaker.setEnabled(!isTablet()); - mTransfer.setEnabled(true); - mPause.setEnabled(true); - mDialer.setEnabled(true); - } - - public void updateStatusFragment(StatusFragment statusFragment) { - mStatus = statusFragment; + return true; } @Override - public void onClick(View v) { - int id = v.getId(); - - if (id == R.id.video) { - int camera = - getPackageManager() - .checkPermission(Manifest.permission.CAMERA, getPackageName()); - Log.i( - "[Permission] Camera permission is " - + (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); - - if (camera == PackageManager.PERMISSION_GRANTED) { - disableVideo(isVideoEnabled(LinphoneManager.getCore().getCurrentCall())); - } else { - checkAndRequestPermission(Manifest.permission.CAMERA, PERMISSIONS_ENABLED_CAMERA); - } - } else if (id == R.id.micro) { - int recordAudio = - getPackageManager() - .checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName()); - Log.i( - "[Permission] Record audio permission is " - + (recordAudio == PackageManager.PERMISSION_GRANTED - ? "granted" - : "denied")); - - if (recordAudio == PackageManager.PERMISSION_GRANTED) { - toggleMicro(); - } else { - checkAndRequestPermission( - Manifest.permission.RECORD_AUDIO, PERMISSIONS_ENABLED_MIC); - } - } else if (id == R.id.speaker) { - toggleSpeaker(); - } else if (id == R.id.add_call) { - goBackToDialer(); - } else if (id == R.id.record_call) { - int externalStorage = - getPackageManager() - .checkPermission( - Manifest.permission.WRITE_EXTERNAL_STORAGE, getPackageName()); - Log.i( - "[Permission] External storage permission is " - + (externalStorage == PackageManager.PERMISSION_GRANTED - ? "granted" - : "denied")); - - if (externalStorage == PackageManager.PERMISSION_GRANTED) { - toggleCallRecording(!mIsRecording); - } else { - checkAndRequestPermission( - Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSIONS_EXTERNAL_STORAGE); - } - } else if (id == R.id.recording) { - toggleCallRecording(false); - } else if (id == R.id.pause) { - pauseOrResumeCall(LinphoneManager.getCore().getCurrentCall()); - } else if (id == R.id.hang_up) { - hangUp(); - } else if (id == R.id.dialer) { - hideOrDisplayNumpad(); - } else if (id == R.id.chat) { - goToChatList(); - } else if (id == R.id.conference) { - enterConference(); - hideOrDisplayCallOptions(); - } else if (id == R.id.switchCamera) { - if (mVideoCallFragment != null) { - mVideoCallFragment.switchCamera(); - } - } else if (id == R.id.transfer) { - goBackToDialerAndDisplayTransferButton(); - } else if (id == R.id.options) { - hideOrDisplayCallOptions(); - } else if (id == R.id.audio_route) { - hideOrDisplayAudioRoutes(); - } else if (id == R.id.route_bluetooth) { - if (BluetoothManager.getInstance().routeAudioToBluetooth()) { - mIsSpeakerEnabled = false; - mRouteBluetooth.setSelected(true); - mRouteSpeaker.setSelected(false); - mRouteEarpiece.setSelected(false); - } - hideOrDisplayAudioRoutes(); - } else if (id == R.id.route_earpiece) { - LinphoneManager.getAudioManager().routeAudioToEarPiece(); - mIsSpeakerEnabled = false; - mRouteBluetooth.setSelected(false); - mRouteSpeaker.setSelected(false); - mRouteEarpiece.setSelected(true); - hideOrDisplayAudioRoutes(); - } else if (id == R.id.route_speaker) { - LinphoneManager.getAudioManager().routeAudioToSpeaker(); - mIsSpeakerEnabled = true; - mRouteBluetooth.setSelected(false); - mRouteSpeaker.setSelected(true); - mRouteEarpiece.setSelected(false); - hideOrDisplayAudioRoutes(); - } else if (id == R.id.call_pause) { - Call call = (Call) v.getTag(); - pauseOrResumeCall(call); - } else if (id == R.id.conference_pause) { - pauseOrResumeConference(); - } + public void onContactsUpdated() { + setCurrentCallContactInformation(); } - private void toggleCallRecording(boolean enable) { - Call call = LinphoneManager.getCore().getCurrentCall(); - + @Override + public void onUserLeaveHint() { + if (mCore == null) return; + Call call = mCore.getCurrentCall(); if (call == null) return; - - if (enable && !mIsRecording) { - call.startRecording(); - Log.d("start call mRecording"); - mRecordCall.setSelected(true); - - mRecording.setVisibility(View.VISIBLE); - mRecording.setEnabled(true); - - mIsRecording = true; - } else if (!enable && mIsRecording) { - call.stopRecording(); - Log.d("stop call mRecording"); - mRecordCall.setSelected(false); - - mRecording.setVisibility(View.GONE); - mRecording.setEnabled(false); - - mIsRecording = false; + boolean videoEnabled = + LinphonePreferences.instance().isVideoEnabled() + && call.getCurrentParams().videoEnabled(); + if (videoEnabled && getResources().getBoolean(R.bool.allow_pip_while_video_call)) { + Compatibility.enterPipMode(this); } } - private void disableVideo(final boolean videoDisabled) { - Core core = LinphoneManager.getCore(); - final Call call = core.getCurrentCall(); - if (call == null) { - return; - } - - if (videoDisabled) { - CallParams params = core.createCallParams(call); - params.enableVideo(false); - call.update(params); - } else { - mVideoProgress.setVisibility(View.VISIBLE); - if (call.getRemoteParams() != null && !call.getRemoteParams().lowBandwidthEnabled()) { - LinphoneManager.getCallManager().addVideo(); - } else { - displayCustomToast(getString(R.string.error_low_bandwidth), Toast.LENGTH_LONG); - } + @Override + public void onPictureInPictureModeChanged( + boolean isInPictureInPictureMode, Configuration newConfig) { + if (isInPictureInPictureMode) { + updateButtonsVisibility(false); } } - private void displayCustomToast(final String message, final int duration) { - LayoutInflater inflater = getLayoutInflater(); - View layout = inflater.inflate(R.layout.toast, (ViewGroup) findViewById(R.id.toastRoot)); - - TextView toastText = layout.findViewById(R.id.toastMessage); - toastText.setText(message); - - final Toast toast = new Toast(getApplicationContext()); - toast.setGravity(Gravity.CENTER, 0, 0); - toast.setDuration(duration); - toast.setView(layout); - toast.show(); + @Override + public void refreshInCallActions() { + updateButtons(); } - private void switchVideo(final boolean displayVideo) { - final Call call = LinphoneManager.getCore().getCurrentCall(); - if (call == null) { - return; - } - - // Check if the call is not terminated - if (call.getState() == State.End || call.getState() == State.Released) return; - - if (!displayVideo) { - showAudioView(); - } else { - if (!call.getRemoteParams().lowBandwidthEnabled()) { - LinphoneManager.getCallManager().addVideo(); - if (mVideoCallFragment == null || !mVideoCallFragment.isVisible()) showVideoView(); - } else { - displayCustomToast(getString(R.string.error_low_bandwidth), Toast.LENGTH_LONG); - } - } + @Override + public void resetCallControlsHidingTimer() { + mHandler.removeCallbacks(mHideControlsRunnable); + mHandler.postDelayed(mHideControlsRunnable, SECONDS_BEFORE_HIDING_CONTROLS); } - private void showAudioView() { - if (LinphoneManager.getCore().getCurrentCall() != null) { - if (!mIsSpeakerEnabled) { - LinphoneManager.getInstance().enableProximitySensing(true); - } - } - replaceFragmentVideoByAudio(); - displayAudioCall(); - showStatusBar(); - removeCallbacks(); + // BUTTONS + + private void updateAudioRouteButtons() { + mRouteSpeaker.setSelected(mAudioManager.isAudioRoutedToSpeaker()); + mRouteBluetooth.setSelected(mAudioManager.isUsingBluetoothAudioRoute()); + mRouteEarpiece.setSelected(mAudioManager.isAudioRoutedToEarpiece()); } - private void showVideoView() { - if (!BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - Log.w("Bluetooth not available, using mSpeaker"); - LinphoneManager.getAudioManager().routeAudioToSpeaker(); - mIsSpeakerEnabled = true; - } - refreshInCallActions(); + private void updateButtons() { + Call call = mCore.getCurrentCall(); - LinphoneManager.getInstance().enableProximitySensing(false); + boolean recordAudioPermissionGranted = checkPermission(Manifest.permission.RECORD_AUDIO); + mCore.enableMic(recordAudioPermissionGranted); + mMicro.setSelected(!mCore.micEnabled()); - replaceFragmentAudioByVideo(); - hideStatusBar(); + mSpeaker.setSelected(mAudioManager.isAudioRoutedToSpeaker()); + + updateAudioRouteButtons(); + + boolean isBluetoothAvailable = mAudioManager.isBluetoothHeadsetConnected(); + mSpeaker.setVisibility(isBluetoothAvailable ? View.GONE : View.VISIBLE); + mAudioRoute.setVisibility(isBluetoothAvailable ? View.VISIBLE : 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 displayNoCurrentCall(boolean display) { - if (!display) { - mActiveCallHeader.setVisibility(View.VISIBLE); - mNoCurrentCall.setVisibility(View.GONE); - } else { - mActiveCallHeader.setVisibility(View.GONE); - mNoCurrentCall.setVisibility(View.VISIBLE); - } - } - - private void displayCallPaused(boolean display) { - if (display) { - mCallPaused.setVisibility(View.VISIBLE); - } else { - mCallPaused.setVisibility(View.GONE); - } - } - - private void displayAudioCall() { - mControlsLayout.setVisibility(View.VISIBLE); - mActiveCallHeader.setVisibility(View.VISIBLE); - mCallInfo.setVisibility(View.VISIBLE); - mAvatarLayout.setVisibility(View.VISIBLE); - mSwitchCamera.setVisibility(View.GONE); - } - - private void replaceFragmentVideoByAudio() { - mAudioCallFragment = new CallAudioFragment(); - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.fragmentContainer, mAudioCallFragment); - try { - transaction.commitAllowingStateLoss(); - } catch (Exception e) { - Log.e(e); - } - } - - private void replaceFragmentAudioByVideo() { - // Hiding controls to let displayVideoCallControlsIfHidden add them plus the callback - mVideoCallFragment = new CallVideoFragment(); - - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.fragmentContainer, mVideoCallFragment); - try { - transaction.commitAllowingStateLoss(); - } catch (Exception e) { - Log.e(e); - } - } - - private void toggleMicro() { - Core core = LinphoneManager.getCore(); - mIsMicMuted = !mIsMicMuted; - core.enableMic(!mIsMicMuted); - mMicro.setSelected(mIsMicMuted); + private void toggleMic() { + mCore.enableMic(!mCore.micEnabled()); + mMicro.setSelected(!mCore.micEnabled()); } private void toggleSpeaker() { - mIsSpeakerEnabled = !mIsSpeakerEnabled; - Core core = LinphoneManager.getCore(); - if (core.getCurrentCall() != null) { - if (isVideoEnabled(core.getCurrentCall())) - LinphoneManager.getInstance().enableProximitySensing(false); - else LinphoneManager.getInstance().enableProximitySensing(!mIsSpeakerEnabled); - } - mSpeaker.setSelected(mIsSpeakerEnabled); - if (mIsSpeakerEnabled) { - LinphoneManager.getAudioManager().routeAudioToSpeaker(); + if (mAudioManager.isAudioRoutedToSpeaker()) { + mAudioManager.routeAudioToEarPiece(); } else { - Log.d("Toggle mSpeaker off, routing back to earpiece"); - LinphoneManager.getAudioManager().routeAudioToEarPiece(); + 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 pauseOrResumeCall(Call call) { - Core core = LinphoneManager.getCore(); - if (call != null && core.getCurrentCall() == call) { + private void togglePause(Call call) { + if (call == null) return; + + if (call == mCore.getCurrentCall()) { call.pause(); - if (isVideoEnabled(core.getCurrentCall())) { - mIsVideoCallPaused = true; - } mPause.setSelected(true); - } else if (call != null) { - if (call.getState() == State.Paused) { - call.resume(); - if (mIsVideoCallPaused) { - mIsVideoCallPaused = false; - } - mPause.setSelected(false); - } + } else if (call.getState() == Call.State.Paused) { + call.resume(); + mPause.setSelected(false); } } - private void hangUp() { - Core core = LinphoneManager.getCore(); - Call currentCall = core.getCurrentCall(); + 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); + } - if (mIsRecording) { - toggleCallRecording(false); - } + 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); + } - if (currentCall != null) { - currentCall.terminate(); - } else if (core.isInConference()) { - core.terminateConference(); + private void toggleRecording() { + Call call = mCore.getCurrentCall(); + if (call == null) return; + + if (call.isRecording()) { + call.stopRecording(); } else { - core.terminateAllCalls(); + call.startRecording(); } + mRecordCall.setSelected(call.isRecording()); + mRecordingInProgress.setVisibility(call.isRecording() ? View.VISIBLE : View.INVISIBLE); } - private void displayVideoCall(boolean display) { - if (display) { - showStatusBar(); - mControlsLayout.setVisibility(View.VISIBLE); - mActiveCallHeader.setVisibility(View.VISIBLE); - mCallInfo.setVisibility(View.VISIBLE); - mAvatarLayout.setVisibility(View.GONE); - mCallsList.setVisibility(View.VISIBLE); - if (mCameraNumber > 1) { - mSwitchCamera.setVisibility(View.VISIBLE); - } + 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 { - hideStatusBar(); - mControlsLayout.setVisibility(View.GONE); - mActiveCallHeader.setVisibility(View.GONE); - mSwitchCamera.setVisibility(View.GONE); - mCallsList.setVisibility(View.GONE); + mMissedMessages.clearAnimation(); + mMissedMessages.setVisibility(View.GONE); } } - public void displayVideoCallControlsIfHidden() { - if (mControlsLayout != null) { - if (mControlsLayout.getVisibility() != View.VISIBLE) { - displayVideoCall(true); - } - resetCallControlsHidingTimer(); + private void updateButtonsVisibility(boolean visible) { + findViewById(R.id.status_bar_fragment).setVisibility(visible ? View.VISIBLE : View.GONE); + mActiveCalls.setVisibility(visible ? View.VISIBLE : View.GONE); + 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) { + mHandler.removeCallbacks(mHideControlsRunnable); } } - private void resetCallControlsHidingTimer() { - if (mControlsHandler != null && mControls != null) { - mControlsHandler.removeCallbacks(mControls); - } - mControls = null; - - if (isVideoEnabled(LinphoneManager.getCore().getCurrentCall()) - && mControlsHandler != null) { - mControlsHandler.postDelayed( - mControls = - new Runnable() { - public void run() { - hideNumpad(); - mVideo.setEnabled(true); - mTransfer.setVisibility(View.INVISIBLE); - mAddCall.setVisibility(View.INVISIBLE); - mConference.setVisibility(View.INVISIBLE); - mRecordCall.setVisibility(View.INVISIBLE); - displayVideoCall(false); - mNumpad.setVisibility(View.GONE); - mOptions.setSelected(false); - } - }, - SECONDS_BEFORE_HIDING_CONTROLS); - } - } - - public void removeCallbacks() { - if (mControlsHandler != null && mControls != null) { - mControlsHandler.removeCallbacks(mControls); - } - mControls = null; - } - - private void hideNumpad() { - if (mNumpad == null || mNumpad.getVisibility() != View.VISIBLE) { + private void updateInterfaceDependingOnVideo() { + Call call = mCore.getCurrentCall(); + if (call == null) { + showVideoControls(false); return; } - mDialer.setImageResource(R.drawable.footer_dialer); - mNumpad.setVisibility(View.GONE); + mVideoInviteInProgress.setVisibility(View.GONE); + mVideo.setEnabled(LinphonePreferences.instance().isVideoEnabled()); + + boolean videoEnabled = + LinphonePreferences.instance().isVideoEnabled() + && call.getCurrentParams().videoEnabled(); + showVideoControls(videoEnabled); + + if (videoEnabled) { + mAudioManager.routeAudioToSpeaker(); + mSpeaker.setSelected(true); + resizePreview(call); + } } - private void hideOrDisplayNumpad() { - if (mNumpad == null) { + private void resizePreview(Call call) { + if (call == null) return; + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + int screenHeight = metrics.heightPixels; + int maxHeight = + screenHeight / 4; // Let's take at most 1/4 of the screen for the camera preview + + VideoDefinition videoSize = + call.getCurrentParams() + .getSentVideoDefinition(); // It already takes care of rotation + if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) { + Log.w( + "[Call Activity] [Video] Couldn't get sent video definition, using default video definition"); + videoSize = call.getCore().getPreferredVideoDefinition(); + } + int width = videoSize.getWidth(); + int height = videoSize.getHeight(); + + Log.d("[Call Activity] [Video] Video height is " + height + ", width is " + width); + width = width * maxHeight / height; + height = maxHeight; + + if (mLocalPreview == null) { + Log.e("[Call Activity] [Video] mCaptureView is null !"); return; } - if (mNumpad.getVisibility() == View.VISIBLE) { - hideNumpad(); - } else { - mDialer.setImageResource(R.drawable.dialer_alt_back); - mNumpad.setVisibility(View.VISIBLE); + RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height); + newLp.addRule( + RelativeLayout.ALIGN_PARENT_BOTTOM, + 1); // Clears the rule, as there is no removeRule until API 17. + newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1); + mLocalPreview.setLayoutParams(newLp); + Log.d("[Call Activity] [Video] Video preview size set to " + width + "x" + height); + } + + private void moveLocalPreview(MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + mPreviewX = (int) motionEvent.getX(); + mPreviewY = (int) motionEvent.getY(); + break; + case MotionEvent.ACTION_MOVE: + int x = (int) motionEvent.getX(); + int y = (int) motionEvent.getY(); + RelativeLayout.LayoutParams lp = + (RelativeLayout.LayoutParams) mLocalPreview.getLayoutParams(); + lp.addRule( + RelativeLayout.ALIGN_PARENT_BOTTOM, + 0); // Clears the rule, as there is no removeRule until API + // 17. + lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0); + int left = lp.leftMargin + (x - mPreviewX); + int top = lp.topMargin + (y - mPreviewY); + lp.leftMargin = left; + lp.topMargin = top; + mLocalPreview.setLayoutParams(lp); + break; } } - private void hideOrDisplayAudioRoutes() { - if (mRouteSpeaker.getVisibility() == View.VISIBLE) { - mRouteSpeaker.setVisibility(View.INVISIBLE); - mRouteBluetooth.setVisibility(View.INVISIBLE); - mRouteEarpiece.setVisibility(View.INVISIBLE); - mAudioRoute.setSelected(false); - } else { - mRouteSpeaker.setVisibility(View.VISIBLE); - mRouteBluetooth.setVisibility(View.VISIBLE); - mRouteEarpiece.setVisibility(View.VISIBLE); - mAudioRoute.setSelected(true); - } - } - - private void hideOrDisplayCallOptions() { - // Hide mOptions - if (mAddCall.getVisibility() == View.VISIBLE) { - mOptions.setSelected(false); - if (mIsTransferAllowed) { - mTransfer.setVisibility(View.INVISIBLE); - } - mAddCall.setVisibility(View.INVISIBLE); - mConference.setVisibility(View.INVISIBLE); - mRecordCall.setVisibility(View.INVISIBLE); - } else { // Display mOptions - if (mIsTransferAllowed) { - mTransfer.setVisibility(View.VISIBLE); - } - mAddCall.setVisibility(View.VISIBLE); - mConference.setVisibility(View.VISIBLE); - mRecordCall.setVisibility(View.VISIBLE); - mOptions.setSelected(true); - mTransfer.setEnabled(LinphoneManager.getCore().getCurrentCall() != null); - } - } + // NAVIGATION private void goBackToDialer() { Intent intent = new Intent(); @@ -1174,857 +839,234 @@ public class CallActivity extends LinphoneGenericActivity 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 (mCountDownTimer != null) { - mCountDownTimer.cancel(); + if (mCallUpdateCountDownTimer != null) { + mCallUpdateCountDownTimer.cancel(); } - - Core core = LinphoneManager.getCore(); - Call call = core.getCurrentCall(); - if (call == null) { - return; - } - - CallParams params = core.createCallParams(call); - if (accept) { - params.enableVideo(true); - core.enableVideoCapture(true); - core.enableVideoDisplay(true); - } - - call.acceptUpdate(params); - } - - private void hideStatusBar() { - if (isTablet()) { - return; - } - - findViewById(R.id.status).setVisibility(View.GONE); - findViewById(R.id.fragmentContainer).setPadding(0, 0, 0, 0); - } - - private void showStatusBar() { - if (isTablet()) { - return; - } - - if (mStatus != null && !mStatus.isVisible()) { - // Hack to ensure statusFragment is visible after coming back to - // mDialer from mChat - mStatus.getView().setVisibility(View.VISIBLE); - } - findViewById(R.id.status).setVisibility(View.VISIBLE); - // findViewById(R.id.fragmentContainer).setPadding(0, - // LinphoneUtils.pixelsToDpi(getResources(), 40), 0, 0); + LinphoneManager.getCallManager().acceptCallUpdate(accept); } private void showAcceptCallUpdateDialog() { - mDialog = new Dialog(this); - mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + 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); - mDialog.setContentView(R.layout.dialog); - mDialog.getWindow() + mCallUpdateDialog.setContentView(R.layout.dialog); + mCallUpdateDialog + .getWindow() .setLayout( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); - mDialog.getWindow().setBackgroundDrawable(d); + mCallUpdateDialog.getWindow().setBackgroundDrawable(d); - TextView customText = mDialog.findViewById(R.id.dialog_message); + TextView customText = mCallUpdateDialog.findViewById(R.id.dialog_message); customText.setText(getResources().getString(R.string.add_video_dialog)); - mDialog.findViewById(R.id.dialog_delete_button).setVisibility(View.GONE); - Button accept = mDialog.findViewById(R.id.dialog_ok_button); + 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 = mDialog.findViewById(R.id.dialog_cancel_button); + Button cancel = mCallUpdateDialog.findViewById(R.id.dialog_cancel_button); cancel.setText(R.string.decline); - mIsVideoAsk = true; accept.setOnClickListener( - new OnClickListener() { + new View.OnClickListener() { @Override public void onClick(View view) { - int camera = - getPackageManager() - .checkPermission( - Manifest.permission.CAMERA, getPackageName()); - Log.i( - "[Permission] Camera permission is " - + (camera == PackageManager.PERMISSION_GRANTED - ? "granted" - : "denied")); - - if (camera == PackageManager.PERMISSION_GRANTED) { + if (checkPermission(Manifest.permission.CAMERA)) { acceptCallUpdate(true); } else { checkAndRequestPermission( - Manifest.permission.CAMERA, PERMISSIONS_REQUEST_CAMERA); + Manifest.permission.CAMERA, CAMERA_TO_ACCEPT_UPDATE); } - mIsVideoAsk = false; - mDialog.dismiss(); - mDialog = null; + mCallUpdateDialog.dismiss(); + mCallUpdateDialog = null; } }); cancel.setOnClickListener( - new OnClickListener() { + new View.OnClickListener() { @Override public void onClick(View view) { acceptCallUpdate(false); - mIsVideoAsk = false; - mDialog.dismiss(); - mDialog = null; + mCallUpdateDialog.dismiss(); + mCallUpdateDialog = null; } }); - mDialog.show(); + mCallUpdateDialog.show(); } - @Override - protected void onResume() { - super.onResume(); + // CONFERENCE - LinphoneManager.getCallManager().setCallInterface(mCallInterface); - Core core = LinphoneManager.getCore(); - if (core != null) { - core.addListener(mListener); - } - mIsSpeakerEnabled = LinphoneManager.getAudioManager().isAudioRoutedToSpeaker(); + private void displayConferenceCall(final Call call) { + LinearLayout conferenceCallView = + (LinearLayout) + LayoutInflater.from(this) + .inflate(R.layout.call_conference_cell, null, false); - refreshIncallUi(); - handleViewIntent(); - - if (mStatus != null && core != null) { - Call currentCall = core.getCurrentCall(); - if (currentCall != null && !currentCall.getAuthenticationTokenVerified()) { - mStatus.showZRTPDialog(currentCall); - } - } - - if (!isVideoEnabled(LinphoneManager.getCore().getCurrentCall())) { - if (!mIsSpeakerEnabled) { - LinphoneManager.getInstance().enableProximitySensing(true); - removeCallbacks(); - } - } - } - - private void handleViewIntent() { - Intent intent = getIntent(); - if (intent != null && "android.intent.action.VIEW".equals(intent.getAction())) { - Call call = LinphoneManager.getCore().getCurrentCall(); - if (call != null && isVideoEnabled(call)) { - Player player = call.getPlayer(); - String path = intent.getData().getPath(); - Log.i("Openning " + path); - /*int openRes = */ player.open(path); - /*if(openRes == -1) { - String message = "Could not open " + path; - Log.e(message); - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); - return; - }*/ - Log.i("Start playing"); - /*if(*/ - player.start() /* == -1) {*/; - /*player.close(); - String message = "Could not start playing " + path; - Log.e(message); - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); - }*/ - } - } - } - - @Override - protected void onPause() { - Core core = LinphoneManager.getCore(); - if (core != null) { - core.removeListener(mListener); - } - LinphoneManager.getCallManager().setCallInterface(null); - - super.onPause(); - - if (mControlsHandler != null && mControls != null) { - mControlsHandler.removeCallbacks(mControls); - } - mControls = null; - } - - @Override - protected void onDestroy() { - LinphoneManager.getInstance().changeStatusToOnline(); - LinphoneManager.getInstance().enableProximitySensing(false); - - unregisterReceiver(mHeadsetReceiver); - - if (mControlsHandler != null && mControls != null) { - mControlsHandler.removeCallbacks(mControls); - } - mControls = null; - mControlsHandler = null; - - unbindDrawables(findViewById(R.id.topLayout)); - if (mTimer != null) { - mTimer.cancel(); - } - - super.onDestroy(); - } - - private void unbindDrawables(View view) { - if (view.getBackground() != null) { - view.getBackground().setCallback(null); - } - if (view instanceof ImageView) { - view.setOnClickListener(null); - } - if (view instanceof ViewGroup && !(view instanceof AdapterView)) { - for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { - unbindDrawables(((ViewGroup) view).getChildAt(i)); - } - ((ViewGroup) view).removeAllViews(); - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LinphoneManager.getAudioManager().onKeyVolumeAdjust(keyCode)) return true; - if (LinphoneUtils.onKeyBackGoHome(this, keyCode, event)) return true; - return super.onKeyDown(keyCode, event); - } - - @Override // Never invoke actually - public void onBackPressed() { - if (mDialog != null) { - acceptCallUpdate(false); - mDialog.dismiss(); - mDialog = null; - } - } - - public void bindAudioFragment(CallAudioFragment fragment) { - mAudioCallFragment = fragment; - } - - public void bindVideoFragment(CallVideoFragment fragment) { - mVideoCallFragment = fragment; - } - - // CALL INFORMATION - private void displayCurrentCall(Call call) { - Address lAddress = call.getRemoteAddress(); - TextView contactName = findViewById(R.id.current_contact_name); - setContactInformation(contactName, lAddress); - registerCallDurationTimer(null, call); - } - - private void displayPausedCalls(final Call call, int index) { - // Control Row - LinearLayout callView; - - if (call == null) { - callView = - (LinearLayout) - mInflater.inflate(R.layout.conference_paused_row, mContainer, false); - callView.setId(index + 1); - callView.setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View view) { - pauseOrResumeConference(); - } - }); + 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 { - callView = - (LinearLayout) mInflater.inflate(R.layout.call_inactive_row, mContainer, false); - callView.setId(index + 1); - - TextView contactName = callView.findViewById(R.id.contact_name); - - Address lAddress = call.getRemoteAddress(); - LinphoneContact lContact = - ContactsManager.getInstance().findContactFromAddress(lAddress); - - if (lContact == null) { - String displayName = LinphoneUtils.getAddressDisplayName(lAddress); - contactName.setText(displayName); - ContactAvatar.displayAvatar(displayName, callView.findViewById(R.id.avatar_layout)); - } else { - contactName.setText(lContact.getFullName()); - ContactAvatar.displayAvatar(lContact, callView.findViewById(R.id.avatar_layout)); - } - - displayCallStatusIconAndReturnCallPaused(callView, call); - registerCallDurationTimer(callView, call); + 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.getConference() != null) { + if (mCore.isInConference()) { + displayConferenceCall(call); + conferenceDisplayed = true; + } else if (!pausedConferenceDisplayed) { + displayPausedConference(); + pausedConferenceDisplayed = true; + } + } else if (call != currentCall) { + 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 setContactInformation(TextView contactName, Address lAddress) { - LinphoneContact lContact = ContactsManager.getInstance().findContactFromAddress(lAddress); - if (lContact == null) { - String displayName = LinphoneUtils.getAddressDisplayName(lAddress); - contactName.setText(displayName); - ContactAvatar.displayAvatar(displayName, mAvatarLayout, true); - } else { - contactName.setText(lContact.getFullName()); - ContactAvatar.displayAvatar(lContact, mAvatarLayout, true); - } - } - - private void displayCallStatusIconAndReturnCallPaused(LinearLayout callView, Call call) { - ImageView onCallStateChanged = callView.findViewById(R.id.call_pause); - onCallStateChanged.setTag(call); - onCallStateChanged.setOnClickListener(this); - - if (call.getState() == State.Paused - || call.getState() == State.PausedByRemote - || call.getState() == State.Pausing) { - onCallStateChanged.setSelected(false); - } - } - - private void registerCallDurationTimer(View v, Call call) { - int callDuration = call.getDuration(); - if (callDuration == 0 && call.getState() != State.StreamsRunning) { - return; - } - - Chronometer timer; - if (v == null) { - timer = findViewById(R.id.current_call_timer); - } else { - timer = v.findViewById(R.id.call_timer); - } - - if (timer == null) { - throw new IllegalArgumentException("no callee_duration view found"); - } - - timer.setBase(SystemClock.elapsedRealtime() - 1000 * callDuration); - timer.start(); - } - - private void refreshCallList() { - Core core = LinphoneManager.getCore(); - mIsConferenceRunning = core.isInConference(); - List pausedCalls = - LinphoneUtils.getCallsInState( - core, Collections.singletonList(State.PausedByRemote)); - - // MultiCalls - if (core.getCallsNb() > 1) { - mCallsList.setVisibility(View.VISIBLE); - } - - // Active call - if (core.getCurrentCall() != null) { - displayNoCurrentCall(false); - if (isVideoEnabled(core.getCurrentCall()) - && !mIsConferenceRunning - && pausedCalls.size() == 0) { - displayVideoCall(false); - } else { - displayAudioCall(); - } - } else { - showAudioView(); - displayNoCurrentCall(true); - if (core.getCallsNb() == 1) { - mCallsList.setVisibility(View.VISIBLE); - } - } - - // Conference - if (mIsConferenceRunning) { - displayConference(true); - } else { - displayConference(false); - } - - if (mCallsList != null) { - mCallsList.removeAllViews(); - int index = 0; - - if (core.getCallsNb() == 0) { - goBackToDialer(); - return; - } - - boolean isConfPaused = false; - for (Call call : core.getCalls()) { - if (call.getConference() != null && !mIsConferenceRunning) { - isConfPaused = true; - index++; - } else { - if (call != core.getCurrentCall() && call.getConference() == null) { - displayPausedCalls(call, index); - index++; - } else { - displayCurrentCall(call); - } - } - } - - if (!mIsConferenceRunning) { - if (isConfPaused) { - mCallsList.setVisibility(View.VISIBLE); - displayPausedCalls(null, index); - } - } - } - - // Paused by remote - if (pausedCalls.size() == 1) { - displayCallPaused(true); - } else { - displayCallPaused(false); - } - } - - // Conference - private void exitConference(final Call call) { - Core core = LinphoneManager.getCore(); - - if (core.isInConference()) { - core.removeFromConference(call); - if (core.getConferenceSize() <= 1) { - core.leaveConference(); - } - } - refreshIncallUi(); - } - - private void enterConference() { - LinphoneManager.getCore().addAllToConference(); - } - - private void pauseOrResumeConference() { - Core core = LinphoneManager.getCore(); - mConferenceStatus = findViewById(R.id.conference_pause); - if (mConferenceStatus != null) { - if (core.isInConference()) { - mConferenceStatus.setSelected(true); - core.leaveConference(); - } else { - mConferenceStatus.setSelected(false); - core.enterConference(); - } - } - refreshCallList(); - } - - private void displayConferenceParticipant(int index, final Call call) { - LinearLayout confView = - (LinearLayout) mInflater.inflate(R.layout.conf_call_control_row, mContainer, false); - mConferenceList.setId(index + 1); - TextView contact = confView.findViewById(R.id.contactNameOrNumber); - - LinphoneContact lContact = - ContactsManager.getInstance().findContactFromAddress(call.getRemoteAddress()); - if (lContact == null) { - contact.setText(call.getRemoteAddress().getUsername()); - } else { - contact.setText(lContact.getFullName()); - } - - registerCallDurationTimer(confView, call); - - ImageView quitConference = confView.findViewById(R.id.quitConference); - quitConference.setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View view) { - exitConference(call); - } - }); - mConferenceList.addView(confView); - } - - private void displayConferenceHeader() { - mConferenceList.setVisibility(View.VISIBLE); - RelativeLayout headerConference = - (RelativeLayout) mInflater.inflate(R.layout.conference_header, mContainer, false); - mConferenceStatus = headerConference.findViewById(R.id.conference_pause); - mConferenceStatus.setOnClickListener(this); - mConferenceList.addView(headerConference); - } - - private void displayConference(boolean isInConf) { - if (isInConf) { - mControlsLayout.setVisibility(View.VISIBLE); - mActiveCallHeader.setVisibility(View.GONE); - mNoCurrentCall.setVisibility(View.GONE); - mConferenceList.removeAllViews(); - - // Conference Header - displayConferenceHeader(); - - // Conference participant - int index = 1; - for (Call call : LinphoneManager.getCore().getCalls()) { - if (call.getConference() != null) { - displayConferenceParticipant(index, call); - index++; - } - } - mConferenceList.setVisibility(View.VISIBLE); - } else { - mConferenceList.setVisibility(View.GONE); - } - } - - private void displayMissedChats() { - int count = 0; - Core core = LinphoneManager.getCore(); - if (core != null) { - count = core.getUnreadChatMessageCountFromActiveLocals(); - } - - if (count > 0) { - mMissedChats.setText(String.valueOf(count)); - mMissedChats.setVisibility(View.VISIBLE); - } else { - mMissedChats.clearAnimation(); - mMissedChats.setVisibility(View.GONE); - } - } - - 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); - } - } - - private void initCallStatsRefresher(final Call call, final View view) { - if (mCallDisplayedInStats == call) return; - - if (mTimer != null && mTask != null) { - mTimer.cancel(); - mTimer = null; - mTask = null; - } - mCallDisplayedInStats = call; - + private void updateCurrentCallTimer() { + Call call = mCore.getCurrentCall(); if (call == 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); - - CallListenerStub mCallListener = - new CallListenerStub() { - public void onStateChanged(Call call, State cstate, String message) { - if (cstate == State.End || cstate == State.Error) { - if (mTimer != null) { - Log.i( - "Call is terminated, stopping mCountDownTimer in charge of stats refreshing."); - mTimer.cancel(); - } - } - } - }; - - mTimer = new Timer(); - mTask = - new TimerTask() { - @Override - public void run() { - if (call == 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 (LinphoneManager.getCore() == null) return; - synchronized (LinphoneManager.getCore()) { - if (call.getState() != Call.State.Released) { - CallParams params = call.getCurrentParams(); - if (params != null) { - CallStats audioStats = - call.getStats(StreamType.Audio); - CallStats videoStats = null; - - if (params.videoEnabled()) - videoStats = - call.getStats(StreamType.Video); - - PayloadType payloadAudio = - params.getUsedAudioPayloadType(); - PayloadType payloadVideo = - params.getUsedVideoPayloadType(); - - formatText( - displayFilter, - getString( - R.string - .call_stats_display_filter), - call.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); - } - } - } - } - }); - } - }; - call.addListener(mCallListener); - mTimer.scheduleAtFixedRate(mTask, 0, 1000); + mCallTimer.setBase(SystemClock.elapsedRealtime() - 1000 * call.getDuration()); + mCallTimer.start(); } - public interface CallActivityInterface { - void setSpeakerEnabled(boolean enable); + private void setCurrentCallContactInformation() { + updateCurrentCallTimer(); - void refreshInCallActions(); + Call call = mCore.getCurrentCall(); + if (call == null) return; - void resetCallControlsHidingTimer(); - } - - //// Earset Connectivity Broadcast innerClass - public class HeadsetReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - if (intent.hasExtra("state")) { - switch (intent.getIntExtra("state", 0)) { - case 0: - if (mOldIsSpeakerEnabled) { - LinphoneManager.getAudioManager().routeAudioToSpeaker(); - mIsSpeakerEnabled = true; - mSpeaker.setEnabled(true); - } - break; - case 1: - LinphoneManager.getAudioManager().routeAudioToEarPiece(); - mOldIsSpeakerEnabled = mIsSpeakerEnabled; - mIsSpeakerEnabled = false; - mSpeaker.setEnabled(false); - break; - } - refreshInCallActions(); - } - } + 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); } } } diff --git a/app/src/main/java/org/linphone/call/CallActivityInterface.java b/app/src/main/java/org/linphone/call/CallActivityInterface.java new file mode 100644 index 000000000..6401bd2a8 --- /dev/null +++ b/app/src/main/java/org/linphone/call/CallActivityInterface.java @@ -0,0 +1,26 @@ +package org.linphone.call; + +/* +CallActivityInterface.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. +*/ + +public interface CallActivityInterface { + void refreshInCallActions(); + + void resetCallControlsHidingTimer(); +} diff --git a/app/src/main/java/org/linphone/call/CallAudioFragment.java b/app/src/main/java/org/linphone/call/CallAudioFragment.java deleted file mode 100644 index 68b20c9c6..000000000 --- a/app/src/main/java/org/linphone/call/CallAudioFragment.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.linphone.call; - -/* -CallAudioFragment.java -Copyright (C) 2017 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.app.Fragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import org.linphone.R; - -public class CallAudioFragment extends Fragment { - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.audio, container, false); - } - - @Override - public void onStart() { - super.onStart(); - CallActivity incallActvityInstance = (CallActivity) getActivity(); - - if (incallActvityInstance != null) { - incallActvityInstance.bindAudioFragment(this); - // Just to be sure we have incall controls - incallActvityInstance.removeCallbacks(); - } - } -} diff --git a/app/src/main/java/org/linphone/call/CallIncomingActivity.java b/app/src/main/java/org/linphone/call/CallIncomingActivity.java index 3c4b497a9..86410f885 100644 --- a/app/src/main/java/org/linphone/call/CallIncomingActivity.java +++ b/app/src/main/java/org/linphone/call/CallIncomingActivity.java @@ -63,6 +63,9 @@ public class CallIncomingActivity extends LinphoneGenericActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } Compatibility.setShowWhenLocked(this, true); Compatibility.setTurnScreenOn(this, true); diff --git a/app/src/main/java/org/linphone/call/CallManager.java b/app/src/main/java/org/linphone/call/CallManager.java index 60d210fdd..8886785d9 100644 --- a/app/src/main/java/org/linphone/call/CallManager.java +++ b/app/src/main/java/org/linphone/call/CallManager.java @@ -44,8 +44,7 @@ import org.linphone.views.AddressType; /** Handle call updating, reinvites. */ public class CallManager { private Context mContext; - private boolean mHandsetON = false; - private CallActivity.CallActivityInterface mCallInterface; + private CallActivityInterface mCallInterface; private BandwidthManager mBandwidthManager; public CallManager(Context context) { @@ -57,90 +56,113 @@ public class CallManager { mBandwidthManager.destroy(); } - public void inviteAddress(Address lAddress, boolean forceZRTP) { - boolean isLowBandwidthConnection = - !LinphoneUtils.isHighBandwidthConnection( - LinphoneService.instance().getApplicationContext()); - - inviteAddress(lAddress, false, isLowBandwidthConnection, forceZRTP); - } - - private void inviteAddress( - Address lAddress, boolean videoEnabled, boolean lowBandwidth, boolean forceZRTP) { - Core core = LinphoneManager.getCore(); - - CallParams params = core.createCallParams(null); - mBandwidthManager.updateWithProfileSettings(params); - - if (videoEnabled && params.videoEnabled()) { - params.enableVideo(true); - } else { - params.enableVideo(false); - } - - if (lowBandwidth) { - params.enableLowBandwidth(true); - Log.d("[Call Manager] Low bandwidth enabled in call params"); - } - - if (forceZRTP) { - params.setMediaEncryption(MediaEncryption.ZRTP); - } - - String recordFile = - FileUtils.getCallRecordingFilename(LinphoneService.instance(), lAddress); - params.setRecordFile(recordFile); - - core.inviteAddressWithParams(lAddress, params); - } - - public void inviteAddress(Address lAddress, boolean videoEnabled, boolean lowBandwidth) { - inviteAddress(lAddress, videoEnabled, lowBandwidth, false); - } - - /** - * Add video to a currently running voice only call. No re-invite is sent if the current call is - * already video or if the bandwidth settings are too low. - * - * @return if updateCall called - */ - public boolean reinviteWithVideo() { + public void terminateCurrentCallOrConferenceOrAll() { Core core = LinphoneManager.getCore(); Call call = core.getCurrentCall(); - if (call == null) { - Log.e("[Call Manager] Trying to reinviteWithVideo while not in call: doing nothing"); - return false; + if (call != null) { + call.terminate(); + } else if (core.isInConference()) { + core.terminateConference(); + } else { + core.terminateAllCalls(); } + } + + public void addVideo() { + Call call = LinphoneManager.getCore().getCurrentCall(); + if (call.getState() == Call.State.End || call.getState() == Call.State.Released) return; + if (!call.getCurrentParams().videoEnabled()) { + enableCamera(call, true); + reinviteWithVideo(); + } + } + + public void removeVideo() { + Core core = LinphoneManager.getCore(); + Call call = core.getCurrentCall(); + CallParams params = core.createCallParams(call); + params.enableVideo(false); + call.update(params); + } + + public void switchCamera() { + Core core = LinphoneManager.getCore(); + try { + String currentDevice = core.getVideoDevice(); + String[] devices = core.getVideoDevicesList(); + int index = 0; + for (String d : devices) { + if (d.equals(currentDevice)) { + break; + } + index++; + } + + String newDevice; + if (index == 1) newDevice = devices[0]; + else if (devices.length > 1) newDevice = devices[1]; + else newDevice = devices[index]; + core.setVideoDevice(newDevice); + + Call call = core.getCurrentCall(); + if (call == null) { + Log.e("[Call Manager] Trying to switch camera while not in call"); + return; + } + call.update(null); + } catch (ArithmeticException ae) { + Log.e("[Call Manager] [Video] Cannot switch camera: no camera"); + } + } + + public boolean acceptCall(Call call) { + if (call == null) return false; + + Core core = LinphoneManager.getCore(); CallParams params = core.createCallParams(call); - if (params.videoEnabled()) return false; + boolean isLowBandwidthConnection = + !LinphoneUtils.isHighBandwidthConnection(LinphoneService.instance()); - // Check if video possible regarding bandwidth limitations - mBandwidthManager.updateWithProfileSettings(params); - - // Abort if not enough bandwidth... - if (!params.videoEnabled()) { + if (params != null) { + params.enableLowBandwidth(isLowBandwidthConnection); + params.setRecordFile( + FileUtils.getCallRecordingFilename(mContext, call.getRemoteAddress())); + } else { + Log.e("[Call Manager] Could not create call params for call"); return false; } - // Not yet in video call: try to re-invite with video - call.update(params); + call.acceptWithParams(params); return true; } - /** - * Change the preferred video size used by linphone core. (impact landscape/portrait buffer). - * Update current call, without reinvite. The camera will be restarted when mediastreamer chain - * is recreated and setParameters is called. - */ - public void updateCall() { + public void acceptCallUpdate(boolean accept) { Core core = LinphoneManager.getCore(); Call call = core.getCurrentCall(); if (call == null) { - Log.e("[Call Manager] Trying to updateCall while not in call: doing nothing"); return; } - call.update(null); + + CallParams params = core.createCallParams(call); + if (accept) { + params.enableVideo(true); + core.enableVideoCapture(true); + core.enableVideoDisplay(true); + } + + call.acceptUpdate(params); + } + + public void inviteAddress(Address address, boolean forceZRTP) { + boolean isLowBandwidthConnection = + !LinphoneUtils.isHighBandwidthConnection(LinphoneService.instance()); + + inviteAddress(address, false, isLowBandwidthConnection, forceZRTP); + } + + public void inviteAddress(Address address, boolean videoEnabled, boolean lowBandwidth) { + inviteAddress(address, videoEnabled, lowBandwidth, false); } public void newOutgoingCall(AddressType address) { @@ -149,10 +171,6 @@ public class CallManager { } public void newOutgoingCall(String to, String displayName) { - // if (mCore.inCall()) { - // listenerDispatcher.tryingNewOutgoingCallButAlreadyInCall(); - // return; - // } if (to == null) return; // If to is only a username, try to find the contact to get an alias if existing @@ -184,8 +202,7 @@ public class CallManager { address.setDisplayName(displayName); boolean isLowBandwidthConnection = - !LinphoneUtils.isHighBandwidthConnection( - LinphoneService.instance().getApplicationContext()); + !LinphoneUtils.isHighBandwidthConnection(LinphoneService.instance()); if (core.isNetworkReachable()) { if (Version.isVideoCapable()) { @@ -210,16 +227,6 @@ public class CallManager { } } - private void enableCamera(Call call, boolean enable) { - if (call != null) { - call.enableCamera(enable); - if (mContext.getResources().getBoolean(R.bool.enable_call_notification)) - LinphoneService.instance() - .getNotificationManager() - .displayCallNotification(LinphoneManager.getCore().getCurrentCall()); - } - } - public void playDtmf(ContentResolver r, char dtmf) { try { if (Settings.System.getInt(r, Settings.System.DTMF_TONE_WHEN_DIALING) == 0) { @@ -233,44 +240,20 @@ public class CallManager { LinphoneManager.getCore().playDtmf(dtmf, -1); } - private void terminateCall() { - Core core = LinphoneManager.getCore(); - if (core.inCall()) { - core.getCurrentCall().terminate(); - } + public boolean shouldShowAcceptCallUpdateDialog(Call call) { + if (call == null) return true; + + boolean remoteVideo = call.getRemoteParams().videoEnabled(); + boolean localVideo = call.getCurrentParams().videoEnabled(); + boolean autoAcceptCameraPolicy = + LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests(); + return remoteVideo + && !localVideo + && !autoAcceptCameraPolicy + && !call.getCore().isInConference(); } - /** @return false if already in video call. */ - public boolean addVideo() { - Call call = LinphoneManager.getCore().getCurrentCall(); - enableCamera(call, true); - return reinviteWithVideo(); - } - - public boolean acceptCall(Call call) { - if (call == null) return false; - - Core core = LinphoneManager.getCore(); - CallParams params = core.createCallParams(call); - - boolean isLowBandwidthConnection = - !LinphoneUtils.isHighBandwidthConnection( - LinphoneService.instance().getApplicationContext()); - - if (params != null) { - params.enableLowBandwidth(isLowBandwidthConnection); - params.setRecordFile( - FileUtils.getCallRecordingFilename(mContext, call.getRemoteAddress())); - } else { - Log.e("[Call Manager] Could not create call params for call"); - return false; - } - - call.acceptWithParams(params); - return true; - } - - public void setCallInterface(CallActivity.CallActivityInterface callInterface) { + public void setCallInterface(CallActivityInterface callInterface) { mCallInterface = callInterface; } @@ -286,20 +269,102 @@ public class CallManager { } } - public void setHandsetMode(Boolean on) { - if (mHandsetON == on) return; + public void removeCallFromConference(Call call) { + if (call == null || call.getConference() == null) { + return; + } + call.getConference().removeParticipant(call.getRemoteAddress()); + + if (call.getCore().getConferenceSize() <= 1) { + call.getCore().leaveConference(); + } + } + + public void pauseConference() { + Core core = LinphoneManager.getCore(); + if (core == null) return; + if (core.isInConference()) { + Log.i("[Call Manager] Pausing conference"); + core.leaveConference(); + } else { + Log.w("[Call Manager] Core isn't in a conference, can't pause it"); + } + } + + public void resumeConference() { + Core core = LinphoneManager.getCore(); + if (core == null) return; + if (!core.isInConference()) { + Log.i("[Call Manager] Resuming conference"); + core.enterConference(); + } else { + Log.w("[Call Manager] Core is already in a conference, can't resume it"); + } + } + + private void inviteAddress( + Address address, boolean videoEnabled, boolean lowBandwidth, boolean forceZRTP) { Core core = LinphoneManager.getCore(); - if (core.isIncomingInvitePending() && on) { - mHandsetON = true; - acceptCall(core.getCurrentCall()); - } else if (on && mCallInterface != null) { - mHandsetON = true; - mCallInterface.setSpeakerEnabled(true); - mCallInterface.refreshInCallActions(); - } else if (!on) { - mHandsetON = false; - terminateCall(); + CallParams params = core.createCallParams(null); + mBandwidthManager.updateWithProfileSettings(params); + + if (videoEnabled && params.videoEnabled()) { + params.enableVideo(true); + } else { + params.enableVideo(false); + } + + if (lowBandwidth) { + params.enableLowBandwidth(true); + Log.d("[Call Manager] Low bandwidth enabled in call params"); + } + + if (forceZRTP) { + params.setMediaEncryption(MediaEncryption.ZRTP); + } + + String recordFile = FileUtils.getCallRecordingFilename(LinphoneService.instance(), address); + params.setRecordFile(recordFile); + + core.inviteAddressWithParams(address, params); + } + + private boolean reinviteWithVideo() { + Core core = LinphoneManager.getCore(); + Call call = core.getCurrentCall(); + if (call == null) { + Log.e("[Call Manager] Trying to add video while not in call"); + return false; + } + if (call.getRemoteParams().lowBandwidthEnabled()) { + Log.e("[Call Manager] Remote has low bandwidth, won't be able to do video"); + return false; + } + + CallParams params = core.createCallParams(call); + if (params.videoEnabled()) return false; + + // Check if video possible regarding bandwidth limitations + mBandwidthManager.updateWithProfileSettings(params); + + // Abort if not enough bandwidth... + if (!params.videoEnabled()) { + return false; + } + + // Not yet in video call: try to re-invite with video + call.update(params); + return true; + } + + private void enableCamera(Call call, boolean enable) { + if (call != null) { + call.enableCamera(enable); + if (mContext.getResources().getBoolean(R.bool.enable_call_notification)) + LinphoneService.instance() + .getNotificationManager() + .displayCallNotification(LinphoneManager.getCore().getCurrentCall()); } } } diff --git a/app/src/main/java/org/linphone/call/CallOutgoingActivity.java b/app/src/main/java/org/linphone/call/CallOutgoingActivity.java index 42e688c73..29b84e3d1 100644 --- a/app/src/main/java/org/linphone/call/CallOutgoingActivity.java +++ b/app/src/main/java/org/linphone/call/CallOutgoingActivity.java @@ -23,12 +23,9 @@ import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.view.Gravity; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -63,6 +60,9 @@ public class CallOutgoingActivity extends LinphoneGenericActivity implements OnC @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.call_outgoing); @@ -89,36 +89,49 @@ public class CallOutgoingActivity extends LinphoneGenericActivity implements OnC if (state == State.Error) { // Convert Core message for internalization if (call.getErrorInfo().getReason() == Reason.Declined) { - displayCustomToast( - getString(R.string.error_call_declined), - Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_call_declined), + Toast.LENGTH_SHORT) + .show(); decline(); } else if (call.getErrorInfo().getReason() == Reason.NotFound) { - displayCustomToast( - getString(R.string.error_user_not_found), - Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_user_not_found), + Toast.LENGTH_SHORT) + .show(); decline(); } else if (call.getErrorInfo().getReason() == Reason.NotAcceptable) { - displayCustomToast( - getString(R.string.error_incompatible_media), - Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_incompatible_media), + Toast.LENGTH_SHORT) + .show(); decline(); } else if (call.getErrorInfo().getReason() == Reason.Busy) { - displayCustomToast( - getString(R.string.error_user_busy), Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_user_busy), + Toast.LENGTH_SHORT) + .show(); decline(); } else if (message != null) { - displayCustomToast( - getString(R.string.error_unknown) + " - " + message, - Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_unknown) + " - " + message, + Toast.LENGTH_SHORT) + .show(); decline(); } } else if (state == State.End) { // Convert Core message for internalization if (call.getErrorInfo().getReason() == Reason.Declined) { - displayCustomToast( - getString(R.string.error_call_declined), - Toast.LENGTH_SHORT); + Toast.makeText( + CallOutgoingActivity.this, + getString(R.string.error_call_declined), + Toast.LENGTH_SHORT) + .show(); decline(); } } else if (state == State.Connected) { @@ -223,20 +236,6 @@ public class CallOutgoingActivity extends LinphoneGenericActivity implements OnC return super.onKeyDown(keyCode, event); } - private void displayCustomToast(final String message, final int duration) { - LayoutInflater inflater = getLayoutInflater(); - View layout = inflater.inflate(R.layout.toast, (ViewGroup) findViewById(R.id.toastRoot)); - - TextView toastText = layout.findViewById(R.id.toastMessage); - toastText.setText(message); - - final Toast toast = new Toast(getApplicationContext()); - toast.setGravity(Gravity.CENTER, 0, 0); - toast.setDuration(duration); - toast.setView(layout); - toast.show(); - } - private void decline() { mCall.terminate(); finish(); diff --git a/app/src/main/java/org/linphone/call/CallStatsFragment.java b/app/src/main/java/org/linphone/call/CallStatsFragment.java new file mode 100644 index 000000000..6c0019fa8 --- /dev/null +++ b/app/src/main/java/org/linphone/call/CallStatsFragment.java @@ -0,0 +1,424 @@ +package org.linphone.call; + +/* +CallStatsFragment.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.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.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 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; + +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; + + @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<>(); + + 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) { + Log.i( + "[Call Stats] Call is terminated, stopping mCountDownTimer in charge of stats refreshing."); + mTimer.cancel(); + } + } + } + }; + + return view; + } + + @Override + public void onResume() { + super.onResume(); + + Core core = LinphoneManager.getCore(); + if (core != null) { + setCall(core.getCurrentCall()); + } + } + + @Override + public void onPause() { + setCall(null); + super.onPause(); + } + + public void setDrawer(DrawerLayout drawer, RelativeLayout content) { + mSideMenu = drawer; + mSideMenuContent = content; + + if (getResources().getBoolean(R.bool.hide_in_call_stats)) { + drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } + } + + public boolean isOpened() { + return mSideMenu != null && mSideMenu.isDrawerVisible(Gravity.LEFT); + } + + public void closeDrawer() { + openOrCloseSideMenu(false, false); + } + + public void openOrCloseSideMenu(boolean open, boolean animate) { + if (mSideMenu == null || mSideMenuContent == null) return; + if (getResources().getBoolean(R.bool.hide_in_call_stats)) return; + + if (open) { + mSideMenu.openDrawer(mSideMenuContent, animate); + } else { + 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/fragments/StatusFragment.java b/app/src/main/java/org/linphone/call/CallStatusBarFragment.java similarity index 77% rename from app/src/main/java/org/linphone/fragments/StatusFragment.java rename to app/src/main/java/org/linphone/call/CallStatusBarFragment.java index 0ecf01c2a..b3656392d 100644 --- a/app/src/main/java/org/linphone/fragments/StatusFragment.java +++ b/app/src/main/java/org/linphone/call/CallStatusBarFragment.java @@ -1,7 +1,8 @@ -package org.linphone.fragments; +package org.linphone.call; + /* -StatusFragment.java -Copyright (C) 2017 Belledonne Communications, Grenoble, France +CallStatusBarFragment.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 @@ -18,7 +19,6 @@ 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.app.Activity; import android.app.Dialog; import android.app.Fragment; import android.content.Context; @@ -37,60 +37,47 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.core.content.ContextCompat; import org.linphone.LinphoneManager; -import org.linphone.LinphoneService; import org.linphone.R; -import org.linphone.call.CallActivity; -import org.linphone.call.CallIncomingActivity; -import org.linphone.call.CallOutgoingActivity; import org.linphone.core.Call; -import org.linphone.core.Content; import org.linphone.core.Core; import org.linphone.core.CoreListenerStub; -import org.linphone.core.Event; import org.linphone.core.MediaEncryption; import org.linphone.core.ProxyConfig; import org.linphone.core.RegistrationState; import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; -public class StatusFragment extends Fragment { +public class CallStatusBarFragment extends Fragment { private final Handler mRefreshHandler = new Handler(); - private TextView mStatusText, mVoicemailCount; - private ImageView mStatusLed, mCallQuality, mEncryption, mMenu, mVoicemail; + private TextView mStatusText; + private ImageView mStatusLed, mCallQuality, mEncryption; private Runnable mCallQualityUpdater; - private boolean mIsInCall, mIsAttached = false; private CoreListenerStub mListener; private Dialog mZrtpDialog = null; private int mDisplayedQuality = -1; - private MenuClikedListener mMenuListener; + private StatsClikedListener mStatsListener; @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.status, container, false); + View view = inflater.inflate(R.layout.call_status_bar, container, false); mStatusText = view.findViewById(R.id.status_text); mStatusLed = view.findViewById(R.id.status_led); mCallQuality = view.findViewById(R.id.call_quality); mEncryption = view.findViewById(R.id.encryption); - mMenu = view.findViewById(R.id.side_menu_button); - mVoicemail = view.findViewById(R.id.voicemail); - mVoicemailCount = view.findViewById(R.id.voicemail_count); - mMenuListener = null; - mMenu.setOnClickListener( + mStatsListener = null; + mCallQuality.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { - if (mMenuListener != null) { - mMenuListener.onMenuCliked(); + if (mStatsListener != null) { + mStatsListener.onStatsClicked(); } } }); - // We create it once to not delay the first display - populateSliderContent(); - mListener = new CoreListenerStub() { @Override @@ -98,11 +85,7 @@ public class StatusFragment extends Fragment { final Core core, final ProxyConfig proxy, final RegistrationState state, - String smessage) { - if (!mIsAttached || !LinphoneService.isReady()) { - return; - } - + String message) { if (core.getProxyConfigList() == null) { mStatusLed.setImageResource(R.drawable.led_disconnected); mStatusText.setText(getString(R.string.no_account)); @@ -136,171 +119,29 @@ public class StatusFragment extends Fragment { } @Override - public void onNotifyReceived( - Core core, Event ev, String eventName, Content content) { - - if (!content.getType().equals("application")) return; - if (!content.getSubtype().equals("simple-message-summary")) return; - - if (content.getSize() == 0) return; - - int unreadCount = 0; - String data = content.getStringBuffer().toLowerCase(); - String[] voiceMail = data.split("voice-message: "); - if (voiceMail.length >= 2) { - final String[] intToParse = voiceMail[1].split("/", 0); - try { - unreadCount = Integer.parseInt(intToParse[0]); - } catch (NumberFormatException nfe) { - Log.e("[Status Fragment] " + nfe); - } - if (unreadCount > 0) { - mVoicemailCount.setText(String.valueOf(unreadCount)); - mVoicemail.setVisibility(View.VISIBLE); - mVoicemailCount.setVisibility(View.VISIBLE); - } else { - mVoicemail.setVisibility(View.GONE); - mVoicemailCount.setVisibility(View.GONE); - } + public void onCallStateChanged( + Core core, Call call, Call.State state, String message) { + if (state == Call.State.Resuming || state == Call.State.StreamsRunning) { + refreshStatusItems(call); } } + + @Override + public void onCallEncryptionChanged( + Core core, Call call, boolean on, String authenticationToken) { + if (call.getCurrentParams() + .getMediaEncryption() + .equals(MediaEncryption.ZRTP) + && !call.getAuthenticationTokenVerified()) { + showZRTPDialog(call); + } + refreshStatusItems(call); + } }; - mIsAttached = true; - Activity activity = getActivity(); - - if (activity instanceof CallActivity) { - ((CallActivity) activity).updateStatusFragment(this); - } - mIsInCall = - activity instanceof CallActivity - || activity instanceof CallIncomingActivity - || activity instanceof CallOutgoingActivity; - return view; } - @Override - public void onDetach() { - super.onDetach(); - mIsAttached = false; - } - - public void setMenuListener(MenuClikedListener listener) { - mMenuListener = listener; - } - - // NORMAL STATUS BAR - - private void populateSliderContent() { - Core core = LinphoneManager.getCore(); - if (core != null) { - mVoicemailCount.setVisibility(View.GONE); - - if (!mIsInCall) { - mVoicemailCount.setVisibility(View.VISIBLE); - } - - if (core.getProxyConfigList().length == 0) { - mStatusLed.setImageResource(R.drawable.led_disconnected); - mStatusText.setText(getString(R.string.no_account)); - } - } - } - - private int getStatusIconResource(RegistrationState state) { - try { - Core core = LinphoneManager.getCore(); - boolean defaultAccountConnected = - (core != null - && core.getDefaultProxyConfig() != null - && core.getDefaultProxyConfig().getState() == RegistrationState.Ok); - if (state == RegistrationState.Ok && defaultAccountConnected) { - return R.drawable.led_connected; - } else if (state == RegistrationState.Progress) { - return R.drawable.led_inprogress; - } else if (state == RegistrationState.Failed) { - return R.drawable.led_error; - } else { - return R.drawable.led_disconnected; - } - } catch (Exception e) { - Log.e(e); - } - - return R.drawable.led_disconnected; - } - - private String getStatusIconText(RegistrationState state) { - Context context = getActivity(); - if (!mIsAttached && LinphoneService.isReady()) context = LinphoneService.instance(); - - try { - if (state == RegistrationState.Ok - && LinphoneManager.getCore().getDefaultProxyConfig().getState() - == RegistrationState.Ok) { - return context.getString(R.string.status_connected); - } else if (state == RegistrationState.Progress) { - return context.getString(R.string.status_in_progress); - } else if (state == RegistrationState.Failed) { - return context.getString(R.string.status_error); - } else { - return context.getString(R.string.status_not_connected); - } - } catch (Exception e) { - Log.e(e); - } - - return context.getString(R.string.status_not_connected); - } - - // INCALL STATUS BAR - private void startCallQuality() { - mCallQuality.setVisibility(View.VISIBLE); - mRefreshHandler.postDelayed( - mCallQualityUpdater = - new Runnable() { - final Call mCurrentCall = LinphoneManager.getCore().getCurrentCall(); - - public void run() { - if (mCurrentCall == null) { - mCallQualityUpdater = null; - return; - } - float newQuality = mCurrentCall.getCurrentQuality(); - updateQualityOfSignalIcon(newQuality); - - if (mIsInCall) { - mRefreshHandler.postDelayed(this, 1000); - } else mCallQualityUpdater = null; - } - }, - 1000); - } - - private void updateQualityOfSignalIcon(float quality) { - int iQuality = (int) quality; - - if (iQuality == mDisplayedQuality) return; - if (quality >= 4) // Good Quality - { - mCallQuality.setImageResource(R.drawable.call_quality_indicator_4); - } else if (quality >= 3) // Average quality - { - mCallQuality.setImageResource(R.drawable.call_quality_indicator_3); - } else if (quality >= 2) // Low quality - { - mCallQuality.setImageResource(R.drawable.call_quality_indicator_2); - } else if (quality >= 1) // Very low quality - { - mCallQuality.setImageResource(R.drawable.call_quality_indicator_1); - } else // Worst quality - { - mCallQuality.setImageResource(R.drawable.call_quality_indicator_0); - } - mDisplayedQuality = iQuality; - } - @Override public void onResume() { super.onResume(); @@ -314,14 +155,15 @@ public class StatusFragment extends Fragment { } Call call = core.getCurrentCall(); - if (mIsInCall - && (call != null || core.getConferenceSize() > 1 || core.getCallsNb() > 0)) { + if (call != null || core.getConferenceSize() > 1 || core.getCallsNb() > 0) { if (call != null) { startCallQuality(); refreshStatusItems(call); + + if (!call.getAuthenticationTokenVerified()) { + showZRTPDialog(call); + } } - mMenu.setVisibility(View.INVISIBLE); - mCallQuality.setVisibility(View.VISIBLE); // We are obviously connected if (core.getDefaultProxyConfig() == null) { @@ -354,9 +196,99 @@ public class StatusFragment extends Fragment { } } + public void setStatsListener(StatsClikedListener listener) { + mStatsListener = listener; + } + + private int getStatusIconResource(RegistrationState state) { + try { + Core core = LinphoneManager.getCore(); + boolean defaultAccountConnected = + (core != null + && core.getDefaultProxyConfig() != null + && core.getDefaultProxyConfig().getState() == RegistrationState.Ok); + if (state == RegistrationState.Ok && defaultAccountConnected) { + return R.drawable.led_connected; + } else if (state == RegistrationState.Progress) { + return R.drawable.led_inprogress; + } else if (state == RegistrationState.Failed) { + return R.drawable.led_error; + } else { + return R.drawable.led_disconnected; + } + } catch (Exception e) { + Log.e(e); + } + + return R.drawable.led_disconnected; + } + + private String getStatusIconText(RegistrationState state) { + Context context = getActivity(); + try { + if (state == RegistrationState.Ok + && LinphoneManager.getCore().getDefaultProxyConfig().getState() + == RegistrationState.Ok) { + return context.getString(R.string.status_connected); + } else if (state == RegistrationState.Progress) { + return context.getString(R.string.status_in_progress); + } else if (state == RegistrationState.Failed) { + return context.getString(R.string.status_error); + } else { + return context.getString(R.string.status_not_connected); + } + } catch (Exception e) { + Log.e(e); + } + + return context.getString(R.string.status_not_connected); + } + + private void startCallQuality() { + mRefreshHandler.postDelayed( + mCallQualityUpdater = + new Runnable() { + final Call mCurrentCall = LinphoneManager.getCore().getCurrentCall(); + + public void run() { + if (mCurrentCall == null) { + mCallQualityUpdater = null; + return; + } + float newQuality = mCurrentCall.getCurrentQuality(); + updateQualityOfSignalIcon(newQuality); + + mRefreshHandler.postDelayed(this, 1000); + } + }, + 1000); + } + + private void updateQualityOfSignalIcon(float quality) { + int iQuality = (int) quality; + + if (iQuality == mDisplayedQuality) return; + if (quality >= 4) // Good Quality + { + mCallQuality.setImageResource(R.drawable.call_quality_indicator_4); + } else if (quality >= 3) // Average quality + { + mCallQuality.setImageResource(R.drawable.call_quality_indicator_3); + } else if (quality >= 2) // Low quality + { + mCallQuality.setImageResource(R.drawable.call_quality_indicator_2); + } else if (quality >= 1) // Very low quality + { + mCallQuality.setImageResource(R.drawable.call_quality_indicator_1); + } else // Worst quality + { + mCallQuality.setImageResource(R.drawable.call_quality_indicator_0); + } + mDisplayedQuality = iQuality; + } + public void refreshStatusItems(final Call call) { if (call != null) { - mVoicemailCount.setVisibility(View.GONE); MediaEncryption mediaEncryption = call.getCurrentParams().getMediaEncryption(); mEncryption.setVisibility(View.VISIBLE); @@ -491,7 +423,7 @@ public class StatusFragment extends Fragment { } } - public interface MenuClikedListener { - void onMenuCliked(); + public interface StatsClikedListener { + void onStatsClicked(); } } diff --git a/app/src/main/java/org/linphone/call/CallVideoFragment.java b/app/src/main/java/org/linphone/call/CallVideoFragment.java deleted file mode 100644 index 4f70de55b..000000000 --- a/app/src/main/java/org/linphone/call/CallVideoFragment.java +++ /dev/null @@ -1,367 +0,0 @@ -package org.linphone.call; - -/* -CallVideoFragment.java -Copyright (C) 2017 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.app.Fragment; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.GestureDetector; -import android.view.GestureDetector.OnDoubleTapListener; -import android.view.GestureDetector.OnGestureListener; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.TextureView; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.widget.RelativeLayout; -import org.linphone.LinphoneManager; -import org.linphone.LinphoneService; -import org.linphone.R; -import org.linphone.compatibility.CompatibilityScaleGestureDetector; -import org.linphone.compatibility.CompatibilityScaleGestureListener; -import org.linphone.core.Call; -import org.linphone.core.Core; -import org.linphone.core.VideoDefinition; -import org.linphone.core.tools.Log; -import org.linphone.settings.LinphonePreferences; -import org.linphone.utils.LinphoneUtils; - -public class CallVideoFragment extends Fragment - implements OnGestureListener, OnDoubleTapListener, CompatibilityScaleGestureListener { - private TextureView mVideoView; - private TextureView mCaptureView; - private GestureDetector mGestureDetector; - private float mZoomFactor = 1.f; - private float mZoomCenterX, mZoomCenterY; - private CompatibilityScaleGestureDetector mScaleDetector; - private CallActivity mInCallActivity; - private int mPreviewX, mPreviewY; - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view; - Core core = LinphoneManager.getCore(); - if (core.hasCrappyOpengl()) { - view = inflater.inflate(R.layout.video_no_opengl, container, false); - } else { - view = inflater.inflate(R.layout.video, container, false); - } - - mVideoView = view.findViewById(R.id.videoSurface); - mCaptureView = view.findViewById(R.id.videoCaptureSurface); - - core.setNativeVideoWindowId(mVideoView); - core.setNativePreviewWindowId(mCaptureView); - - mVideoView.setOnTouchListener( - new OnTouchListener() { - public boolean onTouch(View v, MotionEvent event) { - if (mScaleDetector != null) { - mScaleDetector.onTouchEvent(event); - } - - mGestureDetector.onTouchEvent(event); - if (mInCallActivity != null) { - mInCallActivity.displayVideoCallControlsIfHidden(); - } - return true; - } - }); - - mCaptureView.setOnTouchListener( - new OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - mPreviewX = (int) motionEvent.getX(); - mPreviewY = (int) motionEvent.getY(); - break; - case MotionEvent.ACTION_MOVE: - int x = (int) motionEvent.getX(); - int y = (int) motionEvent.getY(); - RelativeLayout.LayoutParams lp = - (RelativeLayout.LayoutParams) - mCaptureView.getLayoutParams(); - lp.addRule( - RelativeLayout.ALIGN_PARENT_BOTTOM, - 0); // Clears the rule, as there is no removeRule until API - // 17. - lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0); - int left = lp.leftMargin + (x - mPreviewX); - int top = lp.topMargin + (y - mPreviewY); - lp.leftMargin = left; - lp.topMargin = top; - view.setLayoutParams(lp); - break; - } - return true; - } - }); - return view; - } - - @Override - public void onStart() { - super.onStart(); - mInCallActivity = (CallActivity) getActivity(); - if (mInCallActivity != null) { - mInCallActivity.bindVideoFragment(this); - } - } - - private void resizePreview() { - Core core = LinphoneManager.getCore(); - if (core.getCallsNb() > 0) { - Call call = core.getCurrentCall(); - if (call == null) { - call = core.getCalls()[0]; - } - if (call == null) return; - - DisplayMetrics metrics = new DisplayMetrics(); - getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); - int screenHeight = metrics.heightPixels; - int maxHeight = - screenHeight / 4; // Let's take at most 1/4 of the screen for the camera preview - - VideoDefinition videoSize = - call.getCurrentParams() - .getSentVideoDefinition(); // It already takes care of rotation - if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) { - Log.w( - "[Video Fragment] Couldn't get sent video definition, using default video definition"); - videoSize = core.getPreferredVideoDefinition(); - } - int width = videoSize.getWidth(); - int height = videoSize.getHeight(); - - Log.d("[Video Fragment] Video height is " + height + ", width is " + width); - width = width * maxHeight / height; - height = maxHeight; - - if (mCaptureView == null) { - Log.e("[Video Fragment] mCaptureView is null !"); - return; - } - - RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height); - newLp.addRule( - RelativeLayout.ALIGN_PARENT_BOTTOM, - 1); // Clears the rule, as there is no removeRule until API 17. - newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1); - mCaptureView.setLayoutParams(newLp); - Log.d("[Video Fragment] Video preview size set to " + width + "x" + height); - } - } - - public void switchCamera() { - try { - Core core = LinphoneManager.getCore(); - String currentDevice = core.getVideoDevice(); - String[] devices = core.getVideoDevicesList(); - int index = 0; - for (String d : devices) { - if (d.equals(currentDevice)) { - break; - } - index++; - } - - String newDevice; - if (index == 1) newDevice = devices[0]; - else if (devices.length > 1) newDevice = devices[1]; - else newDevice = devices[index]; - core.setVideoDevice(newDevice); - - LinphoneManager.getCallManager().updateCall(); - } catch (ArithmeticException ae) { - Log.e("[Video Fragment] Cannot swtich camera : no camera"); - } - } - - @Override - public void onResume() { - super.onResume(); - - if (LinphonePreferences.instance().isOverlayEnabled()) { - LinphoneService.instance().destroyOverlay(); - } - - mGestureDetector = new GestureDetector(mInCallActivity, this); - mScaleDetector = new CompatibilityScaleGestureDetector(mInCallActivity); - mScaleDetector.setOnScaleListener(this); - - resizePreview(); - } - - @Override - public void onPause() { - 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 - LinphoneService.instance().createOverlay(); - } - } - - super.onPause(); - } - - public boolean onScale(CompatibilityScaleGestureDetector detector) { - mZoomFactor *= detector.getScaleFactor(); - // Don't let the object get too small or too large. - // Zoom to make the video fill the screen vertically - float portraitZoomFactor = - ((float) mVideoView.getHeight()) / (float) ((3 * mVideoView.getWidth()) / 4); - // Zoom to make the video fill the screen horizontally - float landscapeZoomFactor = - ((float) mVideoView.getWidth()) / (float) ((3 * mVideoView.getHeight()) / 4); - mZoomFactor = - Math.max( - 0.1f, - Math.min(mZoomFactor, Math.max(portraitZoomFactor, landscapeZoomFactor))); - - Call currentCall = LinphoneManager.getCore().getCurrentCall(); - if (currentCall != null) { - currentCall.zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); - return true; - } - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - Core core = LinphoneManager.getCore(); - if (LinphoneUtils.isCallEstablished(core.getCurrentCall())) { - if (mZoomFactor > 1) { - // Video is zoomed, slide is used to change center of zoom - if (distanceX > 0 && mZoomCenterX < 1) { - mZoomCenterX += 0.01; - } else if (distanceX < 0 && mZoomCenterX > 0) { - mZoomCenterX -= 0.01; - } - if (distanceY < 0 && mZoomCenterY < 1) { - mZoomCenterY += 0.01; - } else if (distanceY > 0 && mZoomCenterY > 0) { - mZoomCenterY -= 0.01; - } - - if (mZoomCenterX > 1) mZoomCenterX = 1; - if (mZoomCenterX < 0) mZoomCenterX = 0; - if (mZoomCenterY > 1) mZoomCenterY = 1; - if (mZoomCenterY < 0) mZoomCenterY = 0; - - core.getCurrentCall().zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); - return true; - } - } - - return false; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - Core core = LinphoneManager.getCore(); - if (LinphoneUtils.isCallEstablished(core.getCurrentCall())) { - if (mZoomFactor == 1.f) { - // Zoom to make the video fill the screen vertically - float portraitZoomFactor = - ((float) mVideoView.getHeight()) - / (float) ((3 * mVideoView.getWidth()) / 4); - // Zoom to make the video fill the screen horizontally - float landscapeZoomFactor = - ((float) mVideoView.getWidth()) - / (float) ((3 * mVideoView.getHeight()) / 4); - - mZoomFactor = Math.max(portraitZoomFactor, landscapeZoomFactor); - } else { - resetZoom(); - } - - core.getCurrentCall().zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); - return true; - } - - return false; - } - - private void resetZoom() { - mZoomFactor = 1.f; - mZoomCenterX = mZoomCenterY = 0.5f; - } - - @Override - public void onDestroy() { - mInCallActivity = null; - - mCaptureView = null; - if (mVideoView != null) { - mVideoView.setOnTouchListener(null); - mVideoView = null; - } - if (mGestureDetector != null) { - mGestureDetector.setOnDoubleTapListener(null); - mGestureDetector = null; - } - if (mScaleDetector != null) { - mScaleDetector.destroy(); - mScaleDetector = null; - } - - super.onDestroy(); - } - - @Override - public boolean onDown(MotionEvent e) { - return true; // Needed to make the GestureDetector working - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - return false; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return false; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return false; - } - - @Override - public void onLongPress(MotionEvent e) {} - - @Override - public void onShowPress(MotionEvent e) {} - - @Override - public boolean onSingleTapUp(MotionEvent e) { - return false; - } -} diff --git a/app/src/main/java/org/linphone/call/VideoZoomHelper.java b/app/src/main/java/org/linphone/call/VideoZoomHelper.java new file mode 100644 index 000000000..a04ca1b09 --- /dev/null +++ b/app/src/main/java/org/linphone/call/VideoZoomHelper.java @@ -0,0 +1,169 @@ +package org.linphone.call; + +/* +VideoZoomHelper.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.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import org.linphone.LinphoneManager; +import org.linphone.compatibility.CompatibilityScaleGestureDetector; +import org.linphone.compatibility.CompatibilityScaleGestureListener; +import org.linphone.core.Call; +import org.linphone.core.Core; +import org.linphone.utils.LinphoneUtils; + +public class VideoZoomHelper extends GestureDetector.SimpleOnGestureListener + implements CompatibilityScaleGestureListener { + private View mVideoView; + private Context mContext; + private GestureDetector mGestureDetector; + private float mZoomFactor = 1.f; + private float mZoomCenterX, mZoomCenterY; + private CompatibilityScaleGestureDetector mScaleDetector; + + public VideoZoomHelper(Context context, View videoView) { + mContext = context; + mGestureDetector = new GestureDetector(mContext, this); + mScaleDetector = new CompatibilityScaleGestureDetector(mContext); + mScaleDetector.setOnScaleListener(this); + + mVideoView = videoView; + mVideoView.setOnTouchListener( + new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + float currentZoomFactor = mZoomFactor; + if (mScaleDetector != null) { + mScaleDetector.onTouchEvent(event); + } + if (currentZoomFactor != mZoomFactor) { + // We did scale, prevent touch event from going further + return true; + } + + boolean touch = mGestureDetector.onTouchEvent(event); + // If true, gesture detected, prevent touch event from going further + // Otherwise it seems we didn't use event, + // allow it to be dispatched somewhere else + return touch; + } + }); + } + + public boolean onScale(CompatibilityScaleGestureDetector detector) { + mZoomFactor *= detector.getScaleFactor(); + // Don't let the object get too small or too large. + // Zoom to make the video fill the screen vertically + float portraitZoomFactor = + ((float) mVideoView.getHeight()) / (float) ((3 * mVideoView.getWidth()) / 4); + // Zoom to make the video fill the screen horizontally + float landscapeZoomFactor = + ((float) mVideoView.getWidth()) / (float) ((3 * mVideoView.getHeight()) / 4); + mZoomFactor = + Math.max( + 0.1f, + Math.min(mZoomFactor, Math.max(portraitZoomFactor, landscapeZoomFactor))); + + Call currentCall = LinphoneManager.getCore().getCurrentCall(); + if (currentCall != null) { + currentCall.zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); + return true; + } + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + Core core = LinphoneManager.getCore(); + if (LinphoneUtils.isCallEstablished(core.getCurrentCall())) { + if (mZoomFactor > 1) { + // Video is zoomed, slide is used to change center of zoom + if (distanceX > 0 && mZoomCenterX < 1) { + mZoomCenterX += 0.01; + } else if (distanceX < 0 && mZoomCenterX > 0) { + mZoomCenterX -= 0.01; + } + if (distanceY < 0 && mZoomCenterY < 1) { + mZoomCenterY += 0.01; + } else if (distanceY > 0 && mZoomCenterY > 0) { + mZoomCenterY -= 0.01; + } + + if (mZoomCenterX > 1) mZoomCenterX = 1; + if (mZoomCenterX < 0) mZoomCenterX = 0; + if (mZoomCenterY > 1) mZoomCenterY = 1; + if (mZoomCenterY < 0) mZoomCenterY = 0; + + core.getCurrentCall().zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); + return true; + } + } + + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + Core core = LinphoneManager.getCore(); + if (LinphoneUtils.isCallEstablished(core.getCurrentCall())) { + if (mZoomFactor == 1.f) { + // Zoom to make the video fill the screen vertically + float portraitZoomFactor = + ((float) mVideoView.getHeight()) + / (float) ((3 * mVideoView.getWidth()) / 4); + // Zoom to make the video fill the screen horizontally + float landscapeZoomFactor = + ((float) mVideoView.getWidth()) + / (float) ((3 * mVideoView.getHeight()) / 4); + + mZoomFactor = Math.max(portraitZoomFactor, landscapeZoomFactor); + } else { + resetZoom(); + } + + core.getCurrentCall().zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); + return true; + } + + return false; + } + + public void destroy() { + mContext = null; + + if (mVideoView != null) { + mVideoView.setOnTouchListener(null); + mVideoView = null; + } + if (mGestureDetector != null) { + mGestureDetector.setOnDoubleTapListener(null); + mGestureDetector = null; + } + if (mScaleDetector != null) { + mScaleDetector.destroy(); + mScaleDetector = null; + } + } + + private void resetZoom() { + mZoomFactor = 1.f; + mZoomCenterX = mZoomCenterY = 0.5f; + } +} diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java index 44aa50e37..678b71641 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java @@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import static org.linphone.compatibility.Compatibility.CHAT_NOTIFICATIONS_GROUP; import android.annotation.TargetApi; +import android.app.Activity; import android.app.FragmentTransaction; import android.app.Notification; import android.app.NotificationChannel; @@ -30,10 +31,12 @@ import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.os.Build; import android.provider.Settings; import org.linphone.R; +import org.linphone.core.tools.Log; import org.linphone.notifications.Notifiable; import org.linphone.notifications.NotifiableMessage; @@ -260,4 +263,14 @@ class ApiTwentySixPlus { FragmentTransaction transaction, boolean allowed) { transaction.setReorderingAllowed(allowed); } + + public static void enterPipMode(Activity activity) { + boolean supportsPip = + activity.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + Log.i("[Call] Is picture in picture supported: " + supportsPip); + if (supportsPip) { + activity.enterPictureInPictureMode(); + } + } } diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java index 8b647b36e..1be382538 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java @@ -38,8 +38,7 @@ class ApiTwentyThreePlus { public static boolean isDoNotDisturbSettingsAccessGranted(Context context) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - boolean accessGranted = notificationManager.isNotificationPolicyAccessGranted(); - return accessGranted; + return notificationManager.isNotificationPolicyAccessGranted(); } public static boolean isDoNotDisturbPolicyAllowingRinging( diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.java b/app/src/main/java/org/linphone/compatibility/Compatibility.java index 6b525f3c3..0b1473e04 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.java +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.java @@ -256,4 +256,10 @@ public class Compatibility { ApiTwentyFivePlus.updateShortcuts(context); } } + + public static void enterPipMode(Activity activity) { + if (Version.sdkAboveOrEqual(Version.API26_O_80)) { + ApiTwentySixPlus.enterPipMode(activity); + } + } } diff --git a/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java b/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java index 828b122a5..6f2551a8e 100644 --- a/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java +++ b/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java @@ -221,7 +221,7 @@ public class ContactDetailsFragment extends Fragment implements ContactsUpdatedL controls.removeAllViews(); for (LinphoneNumberOrAddress noa : mContact.getNumbersOrAddresses()) { boolean skip; - View v = inflater.inflate(R.layout.contact_control_row, null); + View v = inflater.inflate(R.layout.contact_control_cell, null); String value = noa.getValue(); String displayedNumberOrAddress = value; diff --git a/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java b/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java index 835d112ab..42445c740 100644 --- a/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java +++ b/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java @@ -597,7 +597,7 @@ public class ContactEditorFragment extends Fragment { final LinphoneNumberOrAddress nounoa = tempNounoa; mNumbersAndAddresses.add(nounoa); - final View view = mInflater.inflate(R.layout.contact_edit_row, null); + final View view = mInflater.inflate(R.layout.contact_edit_cell, null); final EditText noa = view.findViewById(R.id.numoraddr); if (!isSIP) { @@ -641,7 +641,7 @@ public class ContactEditorFragment extends Fragment { @SuppressLint("InflateParams") private void addEmptyRowToAllowNewNumberOrAddress( final LinearLayout controls, final boolean isSip) { - final View view = mInflater.inflate(R.layout.contact_edit_row, null); + final View view = mInflater.inflate(R.layout.contact_edit_cell, null); final LinphoneNumberOrAddress nounoa = new LinphoneNumberOrAddress(null, isSip); final EditText noa = view.findViewById(R.id.numoraddr); diff --git a/app/src/main/java/org/linphone/fragments/StatusBarFragment.java b/app/src/main/java/org/linphone/fragments/StatusBarFragment.java new file mode 100644 index 000000000..254a5881d --- /dev/null +++ b/app/src/main/java/org/linphone/fragments/StatusBarFragment.java @@ -0,0 +1,236 @@ +package org.linphone.fragments; + +/* +StatusBarFragment.java +Copyright (C) 2017 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.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import org.linphone.LinphoneManager; +import org.linphone.R; +import org.linphone.core.Content; +import org.linphone.core.Core; +import org.linphone.core.CoreListenerStub; +import org.linphone.core.Event; +import org.linphone.core.ProxyConfig; +import org.linphone.core.RegistrationState; +import org.linphone.core.tools.Log; + +public class StatusBarFragment extends Fragment { + private TextView mStatusText, mVoicemailCount; + private ImageView mStatusLed; + private ImageView mVoicemail; + private CoreListenerStub mListener; + private MenuClikedListener mMenuListener; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.status_bar, container, false); + + mStatusText = view.findViewById(R.id.status_text); + mStatusLed = view.findViewById(R.id.status_led); + ImageView menu = view.findViewById(R.id.side_menu_button); + mVoicemail = view.findViewById(R.id.voicemail); + mVoicemailCount = view.findViewById(R.id.voicemail_count); + + mMenuListener = null; + menu.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + if (mMenuListener != null) { + mMenuListener.onMenuCliked(); + } + } + }); + + // We create it once to not delay the first display + populateSliderContent(); + + mListener = + new CoreListenerStub() { + @Override + public void onRegistrationStateChanged( + final Core core, + final ProxyConfig proxy, + final RegistrationState state, + String smessage) { + if (core.getProxyConfigList() == null) { + mStatusLed.setImageResource(R.drawable.led_disconnected); + mStatusText.setText(getString(R.string.no_account)); + } else { + mStatusLed.setVisibility(View.VISIBLE); + } + + if (core.getDefaultProxyConfig() != null + && core.getDefaultProxyConfig().equals(proxy)) { + mStatusLed.setImageResource(getStatusIconResource(state)); + mStatusText.setText(getStatusIconText(state)); + } else if (core.getDefaultProxyConfig() == null) { + mStatusLed.setImageResource(getStatusIconResource(state)); + mStatusText.setText(getStatusIconText(state)); + } + + try { + mStatusText.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + Core core = LinphoneManager.getCore(); + if (core != null) { + core.refreshRegisters(); + } + } + }); + } catch (IllegalStateException ise) { + Log.e(ise); + } + } + + @Override + public void onNotifyReceived( + Core core, Event ev, String eventName, Content content) { + + if (!content.getType().equals("application")) return; + if (!content.getSubtype().equals("simple-message-summary")) return; + + if (content.getSize() == 0) return; + + int unreadCount = 0; + String data = content.getStringBuffer().toLowerCase(); + String[] voiceMail = data.split("voice-message: "); + if (voiceMail.length >= 2) { + final String[] intToParse = voiceMail[1].split("/", 0); + try { + unreadCount = Integer.parseInt(intToParse[0]); + } catch (NumberFormatException nfe) { + Log.e("[Status Fragment] " + nfe); + } + if (unreadCount > 0) { + mVoicemailCount.setText(String.valueOf(unreadCount)); + mVoicemail.setVisibility(View.VISIBLE); + mVoicemailCount.setVisibility(View.VISIBLE); + } else { + mVoicemail.setVisibility(View.GONE); + mVoicemailCount.setVisibility(View.GONE); + } + } + } + }; + + return view; + } + + @Override + public void onResume() { + super.onResume(); + + Core core = LinphoneManager.getCore(); + if (core != null) { + core.addListener(mListener); + ProxyConfig lpc = core.getDefaultProxyConfig(); + if (lpc != null) { + mListener.onRegistrationStateChanged(core, lpc, lpc.getState(), null); + } + } else { + mStatusText.setVisibility(View.VISIBLE); + } + } + + @Override + public void onPause() { + super.onPause(); + + Core core = LinphoneManager.getCore(); + if (core != null) { + core.removeListener(mListener); + } + } + + public void setMenuListener(MenuClikedListener listener) { + mMenuListener = listener; + } + + private void populateSliderContent() { + Core core = LinphoneManager.getCore(); + if (core != null) { + mVoicemailCount.setVisibility(View.VISIBLE); + + if (core.getProxyConfigList().length == 0) { + mStatusLed.setImageResource(R.drawable.led_disconnected); + mStatusText.setText(getString(R.string.no_account)); + } + } + } + + private int getStatusIconResource(RegistrationState state) { + try { + Core core = LinphoneManager.getCore(); + boolean defaultAccountConnected = + (core != null + && core.getDefaultProxyConfig() != null + && core.getDefaultProxyConfig().getState() == RegistrationState.Ok); + if (state == RegistrationState.Ok && defaultAccountConnected) { + return R.drawable.led_connected; + } else if (state == RegistrationState.Progress) { + return R.drawable.led_inprogress; + } else if (state == RegistrationState.Failed) { + return R.drawable.led_error; + } else { + return R.drawable.led_disconnected; + } + } catch (Exception e) { + Log.e(e); + } + + return R.drawable.led_disconnected; + } + + private String getStatusIconText(RegistrationState state) { + Context context = getActivity(); + try { + if (state == RegistrationState.Ok + && LinphoneManager.getCore().getDefaultProxyConfig().getState() + == RegistrationState.Ok) { + return context.getString(R.string.status_connected); + } else if (state == RegistrationState.Progress) { + return context.getString(R.string.status_in_progress); + } else if (state == RegistrationState.Failed) { + return context.getString(R.string.status_error); + } else { + return context.getString(R.string.status_not_connected); + } + } catch (Exception e) { + Log.e(e); + } + + return context.getString(R.string.status_not_connected); + } + + public interface MenuClikedListener { + void onMenuCliked(); + } +} diff --git a/app/src/main/java/org/linphone/receivers/AccountEnableReceiver.java b/app/src/main/java/org/linphone/receivers/AccountEnableReceiver.java deleted file mode 100644 index 47e5c3b43..000000000 --- a/app/src/main/java/org/linphone/receivers/AccountEnableReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.linphone.receivers; - -/* -AccountEnableReceiver.java -Copyright (C) 2017 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import org.linphone.settings.LinphonePreferences; - -public class AccountEnableReceiver extends BroadcastReceiver { - private static final String TAG = "AccountEnableReceiver"; - private static final String FIELD_ID = "id"; - private static final String FIELD_ACTIVE = "active"; - - @Override - public void onReceive(Context context, Intent intent) { - int prefsAccountIndex = (int) (long) intent.getLongExtra(FIELD_ID, -1); - boolean enable = intent.getBooleanExtra(FIELD_ACTIVE, true); - Log.i(TAG, "Received broadcast for index=" + prefsAccountIndex + ",enable=" + enable); - if (prefsAccountIndex < 0 - || prefsAccountIndex >= LinphonePreferences.instance().getAccountCount()) return; - LinphonePreferences.instance().setAccountEnabled(prefsAccountIndex, enable); - } -} diff --git a/app/src/main/java/org/linphone/receivers/BluetoothManager.java b/app/src/main/java/org/linphone/receivers/BluetoothManager.java deleted file mode 100644 index f8fc814fb..000000000 --- a/app/src/main/java/org/linphone/receivers/BluetoothManager.java +++ /dev/null @@ -1,377 +0,0 @@ -package org.linphone.receivers; -/* -BluetoothManager.java -Copyright (C) 2017 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.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothAssignedNumbers; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import java.util.List; -import org.linphone.LinphoneManager; -import org.linphone.LinphoneService; -import org.linphone.core.tools.Log; - -public class BluetoothManager extends BroadcastReceiver { - private Context mContext; - private AudioManager mAudioManager; - private BluetoothAdapter mBluetoothAdapter; - private BluetoothHeadset mBluetoothHeadset; - private BluetoothDevice mBluetoothDevice; - private BluetoothProfile.ServiceListener mProfileListener; - private boolean mIsBluetoothConnected; - private boolean mIsScoConnected; - - public BluetoothManager() { - mIsBluetoothConnected = false; - if (!ensureInit()) { - android.util.Log.w( - "BluetoothManager", - "[Bluetooth] Manager tried to init but LinphoneService not ready yet..."); - } - initBluetooth(); - } - - public static BluetoothManager getInstance() { - if (LinphoneService.isReady()) { - return LinphoneService.instance().getBluetoothManager(); - } - return null; - } - - private void initBluetooth() { - if (!ensureInit()) { - android.util.Log.w( - "BluetoothManager", - "[Bluetooth] Manager tried to init bluetooth but LinphoneService not ready yet..."); - return; - } - - IntentFilter filter = new IntentFilter(); - filter.addCategory( - BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY - + "." - + BluetoothAssignedNumbers.PLANTRONICS); - filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); - mContext.registerReceiver(this, filter); - android.util.Log.d("BluetoothManager", "[Bluetooth] Receiver started"); - - startBluetooth(); - } - - private void startBluetooth() { - if (mIsBluetoothConnected) { - android.util.Log.e("BluetoothManager", "[Bluetooth] Already started, skipping..."); - return; - } - - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { - if (mProfileListener != null) { - android.util.Log.w( - "BluetoothManager", - "[Bluetooth] Headset profile was already opened, let's close it"); - mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); - } - - mProfileListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (profile == BluetoothProfile.HEADSET) { - android.util.Log.d( - "BluetoothManager", "[Bluetooth] Headset connected"); - mBluetoothHeadset = (BluetoothHeadset) proxy; - mIsBluetoothConnected = true; - } - } - - public void onServiceDisconnected(int profile) { - if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadset = null; - mIsBluetoothConnected = false; - android.util.Log.d( - "BluetoothManager", "[Bluetooth] Headset disconnected"); - LinphoneManager.getAudioManager().routeAudioToEarPiece(); - } - } - }; - boolean success = - mBluetoothAdapter.getProfileProxy( - mContext, mProfileListener, BluetoothProfile.HEADSET); - if (!success) { - android.util.Log.e("BluetoothManager", "[Bluetooth] getProfileProxy failed !"); - } - } else { - android.util.Log.w("BluetoothManager", "[Bluetooth] Interface disabled on device"); - } - } - - private void refreshCallView() { - LinphoneManager.getCallManager().refreshInCallActions(); - } - - private boolean ensureInit() { - if (mBluetoothAdapter == null) { - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - } - if (mContext == null) { - if (LinphoneService.isReady()) { - mContext = LinphoneService.instance().getApplicationContext(); - } else { - return false; - } - } - if (mContext != null && mAudioManager == null) { - mAudioManager = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)); - } - return true; - } - - public boolean routeAudioToBluetooth() { - ensureInit(); - - if (mBluetoothAdapter != null - && mBluetoothAdapter.isEnabled() - && mAudioManager != null - && mAudioManager.isBluetoothScoAvailableOffCall()) { - if (isBluetoothHeadsetAvailable()) { - if (mAudioManager != null && !mAudioManager.isBluetoothScoOn()) { - android.util.Log.d("BluetoothManager", "[Bluetooth] SCO off, let's start it"); - mAudioManager.setBluetoothScoOn(true); - mAudioManager.startBluetoothSco(); - } - } else { - return false; - } - - // Hack to ensure bluetooth sco is really running - boolean ok = isUsingBluetoothAudioRoute(); - int retries = 0; - while (!ok && retries < 5) { - retries++; - - try { - Thread.sleep(200); - } catch (InterruptedException e) { - Log.e(e); - } - - if (mAudioManager != null) { - mAudioManager.setBluetoothScoOn(true); - mAudioManager.startBluetoothSco(); - } - - ok = isUsingBluetoothAudioRoute(); - } - if (ok) { - if (retries > 0) { - android.util.Log.d( - "BluetoothManager", - "[Bluetooth] Audio route ok after " + retries + " retries"); - } else { - android.util.Log.d("BluetoothManager", "[Bluetooth] Audio route ok"); - } - } else { - android.util.Log.d("BluetoothManager", "[Bluetooth] Audio route still not ok..."); - } - - return ok; - } - - return false; - } - - public boolean isUsingBluetoothAudioRoute() { - return mBluetoothHeadset != null - && mBluetoothHeadset.isAudioConnected(mBluetoothDevice) - && mIsScoConnected; - } - - public boolean isBluetoothHeadsetAvailable() { - ensureInit(); - if (mBluetoothAdapter != null - && mBluetoothAdapter.isEnabled() - && mAudioManager != null - && mAudioManager.isBluetoothScoAvailableOffCall()) { - boolean isHeadsetConnected = false; - if (mBluetoothHeadset != null) { - List devices = mBluetoothHeadset.getConnectedDevices(); - mBluetoothDevice = null; - for (final BluetoothDevice dev : devices) { - if (mBluetoothHeadset.getConnectionState(dev) - == BluetoothHeadset.STATE_CONNECTED) { - mBluetoothDevice = dev; - isHeadsetConnected = true; - break; - } - } - android.util.Log.d( - "BluetoothManager", - isHeadsetConnected - ? "[Bluetooth] Headset found, bluetooth audio route available" - : "[Bluetooth] No headset found, bluetooth audio route unavailable"); - } - return isHeadsetConnected; - } - - return false; - } - - public void disableBluetoothSCO() { - if (mAudioManager != null && mAudioManager.isBluetoothScoOn()) { - mAudioManager.stopBluetoothSco(); - mAudioManager.setBluetoothScoOn(false); - - // Hack to ensure bluetooth sco is really stopped - int retries = 0; - while (mIsScoConnected && retries < 10) { - retries++; - - try { - Thread.sleep(200); - } catch (InterruptedException e) { - Log.e(e); - } - - mAudioManager.stopBluetoothSco(); - mAudioManager.setBluetoothScoOn(false); - } - android.util.Log.w("BluetoothManager", "[Bluetooth] SCO disconnected!"); - } - } - - private void stopBluetooth() { - android.util.Log.w("BluetoothManager", "[Bluetooth] Stopping..."); - mIsBluetoothConnected = false; - - disableBluetoothSCO(); - - if (mBluetoothAdapter != null && mProfileListener != null && mBluetoothHeadset != null) { - mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); - mProfileListener = null; - } - mBluetoothDevice = null; - - android.util.Log.w("BluetoothManager", "[Bluetooth] Stopped!"); - - if (LinphoneService.isReady()) { - LinphoneManager.getAudioManager().routeAudioToEarPiece(); - } - - refreshCallView(); - } - - public void destroy() { - try { - stopBluetooth(); - - try { - mContext.unregisterReceiver(this); - android.util.Log.d("BluetoothManager", "[Bluetooth] Receiver stopped"); - } catch (Exception e) { - Log.e(e); - } - } catch (Exception e) { - android.util.Log.e("BluetoothManager", e.getMessage()); - } - } - - public void onReceive(Context context, Intent intent) { - if (!LinphoneService.isReady()) return; - - String action = intent.getAction(); - if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(action)) { - int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 0); - if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) { - android.util.Log.d("BluetoothManager", "[Bluetooth] SCO state: connected"); - // LinphoneManager.getInstance().audioStateChanged(AudioState.BLUETOOTH); - mIsScoConnected = true; - } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { - android.util.Log.d("BluetoothManager", "[Bluetooth] SCO state: disconnected"); - // LinphoneManager.getInstance().audioStateChanged(AudioState.SPEAKER); - mIsScoConnected = false; - } else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) { - android.util.Log.d("BluetoothManager", "[Bluetooth] SCO state: connecting"); - // LinphoneManager.getInstance().audioStateChanged(AudioState.BLUETOOTH); - mIsScoConnected = true; - } else { - android.util.Log.d("BluetoothManager", "[Bluetooth] SCO state: " + state); - } - refreshCallView(); - } else if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { - int state = - intent.getIntExtra( - BluetoothAdapter.EXTRA_CONNECTION_STATE, - BluetoothAdapter.STATE_DISCONNECTED); - if (state == 0) { - android.util.Log.d("BluetoothManager", "[Bluetooth] State: disconnected"); - stopBluetooth(); - } else if (state == 2) { - android.util.Log.d("BluetoothManager", "[Bluetooth] State: connected"); - startBluetooth(); - } else { - android.util.Log.d("BluetoothManager", "[Bluetooth] State: " + state); - } - } else if (intent.getAction() - .equals(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)) { - String command = - intent.getExtras() - .getString(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); - // int type = - // intent.getExtras().getInt(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE); - - Object[] args = - (Object[]) - intent.getExtras() - .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); - if (args.length <= 0) { - android.util.Log.d( - "BluetoothManager", "[Bluetooth] Event: " + command + ", no args"); - return; - } - String eventName = (args[0]).toString(); - if (eventName.equals("BUTTON") && args.length >= 3) { - String buttonID = args[1].toString(); - String mode = args[2].toString(); - android.util.Log.d( - "BluetoothManager", - "[Bluetooth] Event: " - + command - + " : " - + eventName - + ", id = " - + buttonID - + " (" - + mode - + ")"); - } else { - android.util.Log.d( - "BluetoothManager", "[Bluetooth] Event: " + command + " : " + eventName); - } - } - } -} diff --git a/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java b/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java new file mode 100644 index 000000000..f1b23b8f6 --- /dev/null +++ b/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java @@ -0,0 +1,83 @@ +package org.linphone.receivers; + +/* +BluetoothReceiver.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.bluetooth.BluetoothHeadset; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import org.linphone.LinphoneManager; +import org.linphone.core.tools.Log; + +public class BluetoothReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + int state = + intent.getIntExtra( + BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); + if (state == BluetoothHeadset.STATE_CONNECTED) { + Log.i("[Bluetooth] Bluetooth headset connected"); + LinphoneManager.getAudioManager().bluetoothHeadetConnectionChanged(true); + } else if (state == BluetoothHeadset.STATE_DISCONNECTED) { + Log.i("[Bluetooth] Bluetooth headset disconnected"); + LinphoneManager.getAudioManager().bluetoothHeadetConnectionChanged(false); + } else { + Log.w("[Bluetooth] Bluetooth headset unknown state changed: " + state); + } + } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + int state = + intent.getIntExtra( + BluetoothHeadset.EXTRA_STATE, + BluetoothHeadset.STATE_AUDIO_DISCONNECTED); + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + Log.i("[Bluetooth] Bluetooth headset audio connected"); + LinphoneManager.getAudioManager().bluetoothHeadetAudioConnectionChanged(true); + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + Log.i("[Bluetooth] Bluetooth headset audio disconnected"); + LinphoneManager.getAudioManager().bluetoothHeadetAudioConnectionChanged(false); + } else { + Log.w("[Bluetooth] Bluetooth headset unknown audio state changed: " + state); + } + } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) { + int state = + intent.getIntExtra( + AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) { + Log.i("[Bluetooth] Bluetooth headset SCO connected"); + LinphoneManager.getAudioManager().bluetoothHeadetScoConnectionChanged(true); + } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { + Log.i("[Bluetooth] Bluetooth headset SCO disconnected"); + LinphoneManager.getAudioManager().bluetoothHeadetScoConnectionChanged(false); + } else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) { + Log.i("[Bluetooth] Bluetooth headset SCO connecting"); + } else if (state == AudioManager.SCO_AUDIO_STATE_ERROR) { + Log.i("[Bluetooth] Bluetooth headset SCO connection error"); + } else { + Log.w("[Bluetooth] Bluetooth headset unknown SCO state changed: " + state); + } + } else { + Log.w("[Bluetooth] Bluetooth unknown action: " + action); + } + } +} diff --git a/app/src/main/java/org/linphone/receivers/BootReceiver.java b/app/src/main/java/org/linphone/receivers/BootReceiver.java index 8ca3aa57f..0615a50e6 100644 --- a/app/src/main/java/org/linphone/receivers/BootReceiver.java +++ b/app/src/main/java/org/linphone/receivers/BootReceiver.java @@ -2,7 +2,7 @@ package org.linphone.receivers; /* BootReceiver.java -Copyright (C) 2017 Belledonne Communications, Grenoble, France +Copyright (C) 2017 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 diff --git a/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java b/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java new file mode 100644 index 000000000..016ce8c4d --- /dev/null +++ b/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java @@ -0,0 +1,61 @@ +package org.linphone.receivers; + +/* +HeadsetReceiver.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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import org.linphone.LinphoneManager; +import org.linphone.core.tools.Log; + +public class HeadsetReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) { + // This happens when the user plugs a Jack headset to the device for example + int state = intent.getIntExtra("state", 0); + String name = intent.getStringExtra("name"); + int hasMicrophone = intent.getIntExtra("microphone", 0); + + if (state == 0) { + Log.i("[Headset] Headset disconnected:" + name); + } else if (state == 1) { + Log.i("[Headset] Headset connected:" + name); + if (hasMicrophone == 1) { + Log.i("[Headset] Headset " + name + " has a microphone"); + } + } else { + Log.w("[Headset] Unknow headset plugged state: " + state); + } + + LinphoneManager.getAudioManager().routeAudioToEarPiece(); + LinphoneManager.getCallManager().refreshInCallActions(); + } else if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + // This happens when the user disconnect a headset, so we shouldn't play audio loudly + Log.i("[Headset] Noisy state detected, most probably a headset has been disconnected"); + LinphoneManager.getAudioManager().routeAudioToEarPiece(); + LinphoneManager.getCallManager().refreshInCallActions(); + } else { + Log.w("[Headset] Unknow action: " + action); + } + } +} diff --git a/app/src/main/java/org/linphone/receivers/HookReceiver.java b/app/src/main/java/org/linphone/receivers/HookReceiver.java deleted file mode 100644 index b4268028f..000000000 --- a/app/src/main/java/org/linphone/receivers/HookReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.linphone.receivers; - -/* -HookReceiver.java -Copyright (C) 2017 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import org.linphone.LinphoneManager; -import org.linphone.core.tools.Log; - -public class HookReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (isOrderedBroadcast()) abortBroadcast(); - Bundle extras = intent.getExtras(); - boolean handsetOn = extras.getBoolean("hookoff"); - Log.i("[Hook Receiver] Handset " + handsetOn); - - LinphoneManager.getCallManager().setHandsetMode(handsetOn); - if (handsetOn) { - LinphoneManager.getAudioManager().routeAudioToEarPiece(); - } else { - LinphoneManager.getAudioManager().routeAudioToSpeaker(); - } - } -} diff --git a/app/src/main/java/org/linphone/receivers/OutgoingCallReceiver.java b/app/src/main/java/org/linphone/receivers/OutgoingCallReceiver.java deleted file mode 100644 index 025f8d8ec..000000000 --- a/app/src/main/java/org/linphone/receivers/OutgoingCallReceiver.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.linphone.receivers; - -/* -OutgoingCallReceiver.java -Copyright (C) 2017 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 static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import org.linphone.settings.LinphonePreferences; - -public class OutgoingCallReceiver extends BroadcastReceiver { - private static final String TAG = "CallHandler"; - private static final String ACTION_CALL_LINPHONE = "org.linphone.intent.action.CallLaunched"; - - @Override - public void onReceive(Context context, Intent intent) { - LinphonePreferences mPrefs = LinphonePreferences.instance(); - Log.e(TAG, "===>>>> Linphone OutgoingCallReceiver "); - if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) { - Log.e(TAG, "===>>>> Linphone OutgoingCallReceiver : ACTION_NEW_OUTGOING_CALL"); - String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - if (mPrefs.getConfig() != null && mPrefs.getNativeDialerCall()) { - abortBroadcast(); - setResultData(null); - Intent newIntent = new Intent(ACTION_CALL_LINPHONE); - newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); - newIntent.putExtra("StartCall", true); - newIntent.putExtra("NumberToCall", number); - context.startActivity(newIntent); - } - } - } -} diff --git a/app/src/main/java/org/linphone/receivers/PhoneStateChangedReceiver.java b/app/src/main/java/org/linphone/receivers/PhoneStateChangedReceiver.java deleted file mode 100644 index d01dc6d1f..000000000 --- a/app/src/main/java/org/linphone/receivers/PhoneStateChangedReceiver.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.linphone.receivers; - -/* -PhoneStateReceiver.java -Copyright (C) 2017 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.telephony.TelephonyManager; -import org.linphone.LinphoneManager; -import org.linphone.LinphoneService; -import org.linphone.core.Core; - -/** Pause current SIP calls when GSM phone rings or is active. */ -public class PhoneStateChangedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - final String extraState = intent.getStringExtra(TelephonyManager.EXTRA_STATE); - - if (!LinphoneService.isReady()) return; - - if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(extraState) - || TelephonyManager.EXTRA_STATE_RINGING.equals(extraState)) { - LinphoneManager.getInstance().setCallGsmON(true); - Core core = LinphoneManager.getCore(); - if (core != null) { - core.pauseAllCalls(); - } - } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(extraState)) { - LinphoneManager.getInstance().setCallGsmON(false); - } - } -} diff --git a/app/src/main/java/org/linphone/recording/RecordingsActivity.java b/app/src/main/java/org/linphone/recording/RecordingsActivity.java index 13b0c5ac8..f734f9b40 100644 --- a/app/src/main/java/org/linphone/recording/RecordingsActivity.java +++ b/app/src/main/java/org/linphone/recording/RecordingsActivity.java @@ -52,6 +52,10 @@ public class RecordingsActivity extends MainActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (mAbortCreation) { + return; + } + mOnBackPressGoHome = false; mAlwaysHideTabBar = true; diff --git a/app/src/main/java/org/linphone/settings/LinphonePreferences.java b/app/src/main/java/org/linphone/settings/LinphonePreferences.java index bb650b562..1785efd6d 100644 --- a/app/src/main/java/org/linphone/settings/LinphonePreferences.java +++ b/app/src/main/java/org/linphone/settings/LinphonePreferences.java @@ -47,6 +47,7 @@ import org.linphone.core.TunnelConfig; import org.linphone.core.VideoActivationPolicy; import org.linphone.core.VideoDefinition; import org.linphone.core.tools.Log; +import org.linphone.mediastream.Version; import org.linphone.purchase.Purchasable; import org.linphone.utils.LinphoneUtils; @@ -943,6 +944,11 @@ public class LinphonePreferences { } public boolean isOverlayEnabled() { + if (Version.sdkAboveOrEqual(Version.API26_O_80) + && mContext.getResources().getBoolean(R.bool.allow_pip_while_video_call)) { + // Disable overlay and use PIP feature + return false; + } return getConfig().getBool("app", "display_overlay", false); } diff --git a/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java b/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java index 7f3ad4ddd..c0201d108 100644 --- a/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java +++ b/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java @@ -36,6 +36,7 @@ import org.linphone.core.Factory; import org.linphone.core.PayloadType; import org.linphone.core.VideoDefinition; import org.linphone.core.tools.Log; +import org.linphone.mediastream.Version; import org.linphone.settings.widget.ListSetting; import org.linphone.settings.widget.SettingListenerBase; import org.linphone.settings.widget.SwitchSetting; @@ -189,6 +190,11 @@ public class VideoSettingsFragment extends SettingsFragment { mAutoAccept.setChecked(mPrefs.shouldAutomaticallyAcceptVideoRequests()); mOverlay.setChecked(mPrefs.isOverlayEnabled()); + if (Version.sdkAboveOrEqual(Version.API26_O_80) + && getResources().getBoolean(R.bool.allow_pip_while_video_call)) { + // Disable overlay and use PIP feature + mOverlay.setVisibility(View.GONE); + } mBandwidth.setValue(mPrefs.getBandwidthLimit()); mBandwidth.setVisibility( diff --git a/app/src/main/java/org/linphone/utils/AndroidAudioManager.java b/app/src/main/java/org/linphone/utils/AndroidAudioManager.java index d85ac5bfd..e225cfd03 100644 --- a/app/src/main/java/org/linphone/utils/AndroidAudioManager.java +++ b/app/src/main/java/org/linphone/utils/AndroidAudioManager.java @@ -23,7 +23,13 @@ import static android.media.AudioManager.MODE_RINGTONE; import static android.media.AudioManager.STREAM_RING; import static android.media.AudioManager.STREAM_VOICE_CALL; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; @@ -33,6 +39,7 @@ import android.telephony.TelephonyManager; import android.view.KeyEvent; import java.io.FileInputStream; import java.io.IOException; +import java.util.List; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.compatibility.Compatibility; @@ -42,7 +49,8 @@ import org.linphone.core.Core; import org.linphone.core.CoreListenerStub; import org.linphone.core.EcCalibratorStatus; import org.linphone.core.tools.Log; -import org.linphone.receivers.BluetoothManager; +import org.linphone.receivers.BluetoothReceiver; +import org.linphone.receivers.HeadsetReceiver; import org.linphone.settings.LinphonePreferences; public class AndroidAudioManager { @@ -53,10 +61,16 @@ public class AndroidAudioManager { private Call mRingingCall; private MediaPlayer mRingerPlayer; private final Vibrator mVibrator; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothHeadset mBluetoothHeadset; + private BluetoothReceiver mBluetoothReceiver; + private HeadsetReceiver mHeadsetReceiver; private boolean mIsRinging; private boolean mAudioFocused; private boolean mEchoTesterIsRunning; + private boolean mIsBluetoothHeadsetConnected; + private boolean mIsBluetoothHeadsetScoConnected; private CoreListenerStub mListener; @@ -66,6 +80,8 @@ public class AndroidAudioManager { mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mEchoTesterIsRunning = false; + startBluetooth(); + mListener = new CoreListenerStub() { @Override @@ -103,6 +119,15 @@ public class AndroidAudioManager { // mAudioManager.abandonAudioFocus(null); requestAudioFocus(STREAM_VOICE_CALL); } + if (!mIsBluetoothHeadsetConnected) { + if (mContext.getResources().getBoolean(R.bool.isTablet)) { + routeAudioToSpeaker(); + } else { + routeAudioToEarPiece(); + } + } + // Only register this one when a call is active + enableHeadsetReceiver(); } } else if (state == Call.State.End || state == Call.State.Error) { if (core.getCallsNb() == 0) { @@ -118,6 +143,12 @@ public class AndroidAudioManager { mAudioFocused = false; } + // Only register this one when a call is active + if (mHeadsetReceiver != null) { + Log.i("[Audio Manager] Unregistering headset receiver"); + mContext.unregisterReceiver(mHeadsetReceiver); + } + TelephonyManager tm = (TelephonyManager) mContext.getSystemService( @@ -137,12 +168,16 @@ public class AndroidAudioManager { // ringback is heard normally in earpiece or bluetooth receiver. setAudioManagerInCallMode(); requestAudioFocus(STREAM_VOICE_CALL); - startBluetooth(); + if (mIsBluetoothHeadsetConnected) { + routeAudioToBluetooth(); + } } if (state == Call.State.StreamsRunning) { - startBluetooth(); setAudioManagerInCallMode(); + if (mIsBluetoothHeadsetConnected) { + routeAudioToBluetooth(); + } } } @@ -162,6 +197,16 @@ public class AndroidAudioManager { } public void destroy() { + if (mBluetoothAdapter != null && mBluetoothHeadset != null) { + Log.i("[Audio Manager] [Bluetooth] Closing HEADSET profile proxy"); + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); + + Log.i("[Audio Manager] [Bluetooth] Unegistering bluetooth receiver"); + if (mBluetoothReceiver != null) { + mContext.unregisterReceiver(mBluetoothReceiver); + } + } + Core core = LinphoneManager.getCore(); if (core != null) { core.removeListener(mListener); @@ -183,7 +228,11 @@ public class AndroidAudioManager { } public boolean isAudioRoutedToSpeaker() { - return mAudioManager.isSpeakerphoneOn(); + return mAudioManager.isSpeakerphoneOn() && !isUsingBluetoothAudioRoute(); + } + + public boolean isAudioRoutedToEarpiece() { + return !mAudioManager.isSpeakerphoneOn() && !isUsingBluetoothAudioRoute(); } /* Echo cancellation */ @@ -263,12 +312,6 @@ public class AndroidAudioManager { mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); } - private void startBluetooth() { - if (BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - BluetoothManager.getInstance().routeAudioToBluetooth(); - } - } - private void requestAudioFocus(int stream) { if (!mAudioFocused) { int res = @@ -297,10 +340,7 @@ public class AndroidAudioManager { return; } - if (mContext.getResources().getBoolean(R.bool.allow_ringing_while_early_media)) { - routeAudioToSpeaker(); // Need to be able to ear the ringtone during the early media - } - + routeAudioToSpeaker(); mAudioManager.setMode(MODE_RINGTONE); try { @@ -354,23 +394,14 @@ public class AndroidAudioManager { } mIsRinging = false; - if (!BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) { - if (mContext.getResources().getBoolean(R.bool.isTablet)) { - Log.d("[Audio Manager] Stopped ringing, routing back to speaker"); - routeAudioToSpeaker(); - } else { - Log.d("[Audio Manager] Stopped ringing, routing back to earpiece"); - routeAudioToEarPiece(); - } - } } private void routeAudioToSpeakerHelper(boolean speakerOn) { - Log.w( - "[Audio Manager] Routing audio to " - + (speakerOn ? "speaker" : "earpiece") - + ", disabling bluetooth audio route"); - BluetoothManager.getInstance().disableBluetoothSCO(); + Log.w("[Audio Manager] Routing audio to " + (speakerOn ? "speaker" : "earpiece")); + if (mIsBluetoothHeadsetScoConnected) { + Log.w("[Audio Manager] [Bluetooth] Disabling bluetooth audio route"); + changeBluetoothSco(false); + } mAudioManager.setSpeakerphoneOn(speakerOn); } @@ -383,4 +414,187 @@ public class AndroidAudioManager { i < 0 ? AudioManager.ADJUST_LOWER : AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI); } + + // Bluetooth + + public synchronized void bluetoothHeadetConnectionChanged(boolean connected) { + mIsBluetoothHeadsetConnected = connected; + mAudioManager.setBluetoothScoOn(connected); + } + + public synchronized void bluetoothHeadetAudioConnectionChanged(boolean connected) { + mIsBluetoothHeadsetScoConnected = connected; + mAudioManager.setBluetoothScoOn(connected); + } + + public synchronized boolean isBluetoothHeadsetConnected() { + return mIsBluetoothHeadsetConnected; + } + + public synchronized void bluetoothHeadetScoConnectionChanged(boolean connected) { + mIsBluetoothHeadsetScoConnected = connected; + LinphoneManager.getCallManager().refreshInCallActions(); + } + + public synchronized boolean isUsingBluetoothAudioRoute() { + return mIsBluetoothHeadsetScoConnected; + } + + public synchronized void routeAudioToBluetooth() { + if (!isBluetoothHeadsetConnected()) { + Log.w("[Audio Manager] [Bluetooth] No headset connected"); + return; + } + if (mAudioManager.getMode() != AudioManager.MODE_IN_COMMUNICATION) { + Log.w( + "[Audio Manager] [Bluetooth] Changing audio mode to MODE_IN_COMMUNICATION and requesting STREAM_VOICE_CALL focus"); + mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + requestAudioFocus(STREAM_VOICE_CALL); + } + changeBluetoothSco(true); + } + + private synchronized void changeBluetoothSco(final boolean enable) { + // IT WILL TAKE A CERTAIN NUMBER OF CALLS TO EITHER START/STOP BLUETOOTH SCO FOR IT TO WORK + if (enable && mIsBluetoothHeadsetScoConnected) { + Log.i("[Audio Manager] [Bluetooth] SCO already enabled, skipping"); + return; + } else if (!enable && !mIsBluetoothHeadsetScoConnected) { + Log.i("[Audio Manager] [Bluetooth] SCO already disabled, skipping"); + return; + } + + new Thread() { + @Override + public void run() { + boolean resultAcknoledged; + int retries = 0; + do { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Log.e(e); + } + + synchronized (AndroidAudioManager.this) { + if (enable) { + Log.i( + "[Audio Manager] [Bluetooth] Starting SCO: try number " + + retries); + mAudioManager.startBluetoothSco(); + } else { + Log.i( + "[Audio Manager] [Bluetooth] Stopping SCO: try number " + + retries); + mAudioManager.stopBluetoothSco(); + } + resultAcknoledged = isUsingBluetoothAudioRoute() == enable; + retries++; + } + } while (!resultAcknoledged && retries < 10); + } + }.start(); + } + + private void startBluetooth() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + Log.i("[Audio Manager] [Bluetooth] Adapter found"); + if (mAudioManager.isBluetoothScoAvailableOffCall()) { + Log.i("[Audio Manager] [Bluetooth] SCO available off call, continue"); + } else { + Log.w("[Audio Manager] [Bluetooth] SCO not available off call !"); + } + if (mBluetoothAdapter.isEnabled()) { + Log.i("[Audio Manager] [Bluetooth] Adapter enabled"); + mBluetoothReceiver = new BluetoothReceiver(); + mIsBluetoothHeadsetConnected = false; + mIsBluetoothHeadsetScoConnected = false; + + BluetoothProfile.ServiceListener bluetoothServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.HEADSET) { + Log.i("[Audio Manager] [Bluetooth] HEADSET profile connected"); + mBluetoothHeadset = (BluetoothHeadset) proxy; + + List devices = + mBluetoothHeadset.getConnectedDevices(); + if (devices.size() > 0) { + Log.i( + "[Audio Manager] [Bluetooth] A device is already connected"); + bluetoothHeadetConnectionChanged(true); + } + + Log.i( + "[Audio Manager] [Bluetooth] Registering bluetooth receiver"); + + mContext.registerReceiver( + mBluetoothReceiver, + new IntentFilter( + BluetoothHeadset + .ACTION_CONNECTION_STATE_CHANGED)); + mContext.registerReceiver( + mBluetoothReceiver, + new IntentFilter( + BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)); + Intent sticky = + mContext.registerReceiver( + mBluetoothReceiver, + new IntentFilter( + AudioManager + .ACTION_SCO_AUDIO_STATE_UPDATED)); + int state = + sticky.getIntExtra( + AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) { + Log.i( + "[Audio Manager] [Bluetooth] Bluetooth headset SCO connected"); + bluetoothHeadetScoConnectionChanged(true); + } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { + Log.i( + "[Audio Manager] [Bluetooth] Bluetooth headset SCO disconnected"); + bluetoothHeadetScoConnectionChanged(false); + } else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) { + Log.i( + "[Audio Manager] [Bluetooth] Bluetooth headset SCO connecting"); + } else if (state == AudioManager.SCO_AUDIO_STATE_ERROR) { + Log.i( + "[Audio Manager] [Bluetooth] Bluetooth headset SCO connection error"); + } else { + Log.w( + "[Audio Manager] [Bluetooth] Bluetooth headset unknown SCO state changed: " + + state); + } + } + } + + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.HEADSET) { + Log.i( + "[Audio Manager] [Bluetooth] HEADSET profile disconnected"); + mBluetoothHeadset = null; + mIsBluetoothHeadsetConnected = false; + mIsBluetoothHeadsetScoConnected = false; + } + } + }; + mBluetoothAdapter.getProfileProxy( + mContext, bluetoothServiceListener, BluetoothProfile.HEADSET); + } + } + } + + // HEADSET + + private void enableHeadsetReceiver() { + mHeadsetReceiver = new HeadsetReceiver(); + + Log.i("[Audio Manager] Registering headset receiver"); + mContext.registerReceiver( + mHeadsetReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + mContext.registerReceiver( + mHeadsetReceiver, new IntentFilter(AudioManager.ACTION_HEADSET_PLUG)); + } } diff --git a/app/src/main/java/org/linphone/views/Digit.java b/app/src/main/java/org/linphone/views/Digit.java index 9701ad8a7..518dbc25e 100644 --- a/app/src/main/java/org/linphone/views/Digit.java +++ b/app/src/main/java/org/linphone/views/Digit.java @@ -27,10 +27,10 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.Button; -import android.widget.Toast; import org.linphone.LinphoneManager; import org.linphone.LinphoneService; import org.linphone.R; +import org.linphone.core.Call; import org.linphone.core.Core; import org.linphone.core.tools.Log; import org.linphone.settings.LinphonePreferences; @@ -95,12 +95,7 @@ public class Digit extends Button implements AddressAware { private boolean linphoneServiceReady() { if (!LinphoneService.isReady()) { - Log.w("Service is not ready while pressing digit"); - Toast.makeText( - getContext(), - getContext().getString(R.string.skipable_error_service_not_ready), - Toast.LENGTH_SHORT) - .show(); + Log.e("[Numpad] Service is not ready while pressing digit"); return false; } return true; @@ -112,18 +107,19 @@ public class Digit extends Button implements AddressAware { Core core = LinphoneManager.getCore(); core.stopDtmf(); mIsDtmfStarted = false; - if (core.inCall()) { - core.getCurrentCall().sendDtmf(mKeyCode); + Call call = core.getCurrentCall(); + if (call != null) { + call.sendDtmf(mKeyCode); } } if (mAddress != null) { - int lBegin = mAddress.getSelectionStart(); - if (lBegin == -1) { - lBegin = mAddress.length(); + int begin = mAddress.getSelectionStart(); + if (begin == -1) { + begin = mAddress.length(); } - if (lBegin >= 0) { - mAddress.getEditableText().insert(lBegin, String.valueOf(mKeyCode)); + if (begin >= 0) { + mAddress.getEditableText().insert(begin, String.valueOf(mKeyCode)); } if (LinphonePreferences.instance().getDebugPopupAddress() != null @@ -212,12 +208,12 @@ public class Digit extends Button implements AddressAware { if (mAddress == null) return true; - int lBegin = mAddress.getSelectionStart(); - if (lBegin == -1) { - lBegin = mAddress.getEditableText().length(); + int begin = mAddress.getSelectionStart(); + if (begin == -1) { + begin = mAddress.getEditableText().length(); } - if (lBegin >= 0) { - mAddress.getEditableText().insert(lBegin, "+"); + if (begin >= 0) { + mAddress.getEditableText().insert(begin, "+"); } return true; } diff --git a/app/src/main/java/org/linphone/views/Numpad.java b/app/src/main/java/org/linphone/views/Numpad.java index 3dbbcdbf7..5e3de2314 100644 --- a/app/src/main/java/org/linphone/views/Numpad.java +++ b/app/src/main/java/org/linphone/views/Numpad.java @@ -2,7 +2,7 @@ package org.linphone.views; /* NumpadView.java -Copyright (C) 2017 Belledonne Communications, Grenoble, France +Copyright (C) 2017 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 @@ -31,21 +31,13 @@ import java.util.Collection; import org.linphone.R; public class Numpad extends LinearLayout implements AddressAware { - private final boolean mPlayDtmf; - public Numpad(Context context, boolean playDtmf) { - super(context); - mPlayDtmf = playDtmf; - LayoutInflater.from(context).inflate(R.layout.numpad, this); - setLongClickable(true); - onFinishInflate(); - } - public Numpad(Context context, AttributeSet attrs) { super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Numpad); - mPlayDtmf = 1 == a.getInt(org.linphone.R.styleable.Numpad_play_dtmf, 1); + mPlayDtmf = 1 == a.getInt(R.styleable.Numpad_play_dtmf, 1); a.recycle(); LayoutInflater.from(context).inflate(R.layout.numpad, this); setLongClickable(true); diff --git a/app/src/main/res/drawable/route_bluetooth.xml b/app/src/main/res/drawable/route_bluetooth.xml index ea4daaf07..072a96f2c 100644 --- a/app/src/main/res/drawable/route_bluetooth.xml +++ b/app/src/main/res/drawable/route_bluetooth.xml @@ -4,6 +4,10 @@ + + + diff --git a/app/src/main/res/drawable/route_earpiece.xml b/app/src/main/res/drawable/route_earpiece.xml index f8d501869..3549a4b20 100644 --- a/app/src/main/res/drawable/route_earpiece.xml +++ b/app/src/main/res/drawable/route_earpiece.xml @@ -4,6 +4,10 @@ + + + diff --git a/app/src/main/res/drawable/route_speaker.xml b/app/src/main/res/drawable/route_speaker.xml index 5121b9298..6d4d54da5 100644 --- a/app/src/main/res/drawable/route_speaker.xml +++ b/app/src/main/res/drawable/route_speaker.xml @@ -4,6 +4,10 @@ + + + diff --git a/app/src/main/res/drawable/routes.xml b/app/src/main/res/drawable/routes.xml index c3d06a0df..3d0fd6f0a 100644 --- a/app/src/main/res/drawable/routes.xml +++ b/app/src/main/res/drawable/routes.xml @@ -4,6 +4,10 @@ + + + diff --git a/app/src/main/res/layout-land/about.xml b/app/src/main/res/layout-land/about.xml index 27cc81442..ed6924ab0 100644 --- a/app/src/main/res/layout-land/about.xml +++ b/app/src/main/res/layout-land/about.xml @@ -8,21 +8,19 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + android:columnCount="2" + android:padding="20dp"> @@ -53,10 +51,9 @@ @@ -75,9 +72,10 @@ android:layout_height="wrap_content" android:layout_gravity="left" android:text="@string/about_liblinphone_sdk_version" /> + - + + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> - - + android:layout_height="match_parent" /> + + - - - + + android:layout_height="match_parent"> - - - - - - remote_pause - + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:gravity="bottom"> - + + + + + + - + + + - + + tools:layout="@layout/status_bar" /> diff --git a/app/src/main/res/layout-land/main.xml b/app/src/main/res/layout-land/main.xml index 8511680cd..2eeaadd85 100644 --- a/app/src/main/res/layout-land/main.xml +++ b/app/src/main/res/layout-land/main.xml @@ -6,10 +6,10 @@ + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> diff --git a/app/src/main/res/layout/assistant_account_connection.xml b/app/src/main/res/layout/assistant_account_connection.xml index 6dfd94813..2dfbeb800 100644 --- a/app/src/main/res/layout/assistant_account_connection.xml +++ b/app/src/main/res/layout/assistant_account_connection.xml @@ -7,10 +7,10 @@ + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/call.xml b/app/src/main/res/layout/call.xml index 29708add8..32ea83df2 100644 --- a/app/src/main/res/layout/call.xml +++ b/app/src/main/res/layout/call.xml @@ -10,66 +10,37 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + android:layout_height="match_parent"/> + + - - - + + android:layout_height="match_parent"> - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_alignParentBottom="true" /> - - - - - - - - - - - - - - - - - - - + - + + + - + + tools:layout="@layout/status_bar" /> diff --git a/app/src/main/res/layout/call_active_header.xml b/app/src/main/res/layout/call_active_header.xml new file mode 100644 index 000000000..2b0339fe3 --- /dev/null +++ b/app/src/main/res/layout/call_active_header.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_conference_cell.xml b/app/src/main/res/layout/call_conference_cell.xml new file mode 100644 index 000000000..eac7d8a1b --- /dev/null +++ b/app/src/main/res/layout/call_conference_cell.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_conference_header.xml b/app/src/main/res/layout/call_conference_header.xml new file mode 100644 index 000000000..0498e1cad --- /dev/null +++ b/app/src/main/res/layout/call_conference_header.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_paused_row.xml b/app/src/main/res/layout/call_conference_paused_cell.xml similarity index 86% rename from app/src/main/res/layout/conference_paused_row.xml rename to app/src/main/res/layout/call_conference_paused_cell.xml index 7f55b68fd..e0abe6b26 100644 --- a/app/src/main/res/layout/conference_paused_row.xml +++ b/app/src/main/res/layout/call_conference_paused_cell.xml @@ -2,7 +2,6 @@ diff --git a/app/src/main/res/layout/call_incoming.xml b/app/src/main/res/layout/call_incoming.xml index 5b34690ff..1d907cdd1 100644 --- a/app/src/main/res/layout/call_incoming.xml +++ b/app/src/main/res/layout/call_incoming.xml @@ -7,10 +7,10 @@ + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_primary_buttons.xml b/app/src/main/res/layout/call_primary_buttons.xml new file mode 100644 index 000000000..6db6b8744 --- /dev/null +++ b/app/src/main/res/layout/call_primary_buttons.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_secondary_buttons.xml b/app/src/main/res/layout/call_secondary_buttons.xml new file mode 100644 index 000000000..61532ae7d --- /dev/null +++ b/app/src/main/res/layout/call_secondary_buttons.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/incall_stats.xml b/app/src/main/res/layout/call_stats.xml similarity index 100% rename from app/src/main/res/layout/incall_stats.xml rename to app/src/main/res/layout/call_stats.xml diff --git a/app/src/main/res/layout/call_status_bar.xml b/app/src/main/res/layout/call_status_bar.xml new file mode 100644 index 000000000..bd85efd46 --- /dev/null +++ b/app/src/main/res/layout/call_status_bar.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat.xml b/app/src/main/res/layout/chat.xml index d4bfaf0bf..eab351439 100644 --- a/app/src/main/res/layout/chat.xml +++ b/app/src/main/res/layout/chat.xml @@ -102,7 +102,7 @@ android:src="@drawable/delete" /> - + diff --git a/app/src/main/res/layout/chatlist.xml b/app/src/main/res/layout/chatlist.xml index 4e35ed443..54fee5e43 100644 --- a/app/src/main/res/layout/chatlist.xml +++ b/app/src/main/res/layout/chatlist.xml @@ -63,7 +63,7 @@ android:src="@drawable/delete" /> - + - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/conference_header.xml b/app/src/main/res/layout/conference_header.xml deleted file mode 100644 index e4c0bd408..000000000 --- a/app/src/main/res/layout/conference_header.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/contact_control_row.xml b/app/src/main/res/layout/contact_control_cell.xml similarity index 100% rename from app/src/main/res/layout/contact_control_row.xml rename to app/src/main/res/layout/contact_control_cell.xml diff --git a/app/src/main/res/layout/contact_edit_row.xml b/app/src/main/res/layout/contact_edit_cell.xml similarity index 100% rename from app/src/main/res/layout/contact_edit_row.xml rename to app/src/main/res/layout/contact_edit_cell.xml diff --git a/app/src/main/res/layout/contacts_list.xml b/app/src/main/res/layout/contacts_list.xml index d1f1be4cc..2833ba3f4 100644 --- a/app/src/main/res/layout/contacts_list.xml +++ b/app/src/main/res/layout/contacts_list.xml @@ -85,7 +85,7 @@ - + + android:orientation="horizontal"> - diff --git a/app/src/main/res/layout/history.xml b/app/src/main/res/layout/history.xml index 9671cfd25..5c1543291 100644 --- a/app/src/main/res/layout/history.xml +++ b/app/src/main/res/layout/history.xml @@ -79,7 +79,7 @@ - + + tools:layout="@layout/status_bar" /> + tools:layout="@layout/status_bar" /> - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/preference_led.xml b/app/src/main/res/layout/preference_led.xml deleted file mode 100644 index 31fdc43f9..000000000 --- a/app/src/main/res/layout/preference_led.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/preference_list_content.xml b/app/src/main/res/layout/preference_list_content.xml deleted file mode 100644 index 36497c5ef..000000000 --- a/app/src/main/res/layout/preference_list_content.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/recordings_list.xml b/app/src/main/res/layout/recordings_list.xml index 2ccd6d256..2a1926cd3 100644 --- a/app/src/main/res/layout/recordings_list.xml +++ b/app/src/main/res/layout/recordings_list.xml @@ -44,7 +44,7 @@ android:src="@drawable/delete" /> - + - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/status.xml b/app/src/main/res/layout/status_bar.xml similarity index 69% rename from app/src/main/res/layout/status.xml rename to app/src/main/res/layout/status_bar.xml index f482390cf..a6dcd0a57 100644 --- a/app/src/main/res/layout/status.xml +++ b/app/src/main/res/layout/status_bar.xml @@ -16,17 +16,6 @@ android:padding="10dp" android:src="@drawable/menu" /> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/toast.xml b/app/src/main/res/layout/toast.xml deleted file mode 100644 index b0fe03c58..000000000 --- a/app/src/main/res/layout/toast.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/tuto_carddav.xml b/app/src/main/res/layout/tuto_carddav.xml deleted file mode 100644 index 6e78b6b73..000000000 --- a/app/src/main/res/layout/tuto_carddav.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - -