Feature/reworked call code

This commit is contained in:
Sylvain Berfini 2019-05-10 17:15:06 +02:00
parent 81588d6906
commit a1ee586a13
103 changed files with 3242 additions and 4341 deletions

View file

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- Needed for bluetooth headset -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@ -183,8 +184,9 @@
<activity
android:name=".call.CallActivity"
android:launchMode="singleTop"
android:noHistory="true"
android:showWhenLocked="true"/>
android:showWhenLocked="true"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"/>
<!-- Assistant activities -->
@ -252,10 +254,6 @@
<!-- Receivers -->
<receiver
android:name=".receivers.BluetoothManager"
android:enabled="false"/>
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@ -265,12 +263,6 @@
</intent-filter>
</receiver>
<receiver android:name=".receivers.PhoneStateChangedReceiver">
<intent-filter android:priority="999">
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
<receiver
android:name=".notifications.NotificationBroadcastReceiver"
android:enabled="true"

View file

@ -22,11 +22,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@ -37,6 +35,8 @@ import android.net.Uri;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
@ -73,8 +73,6 @@ import org.linphone.core.tools.H264Helper;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration;
import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration.AndroidCamera;
import org.linphone.receivers.HookReceiver;
import org.linphone.receivers.OutgoingCallReceiver;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.AndroidAudioManager;
import org.linphone.utils.LinphoneUtils;
@ -98,8 +96,8 @@ public class LinphoneManager implements SensorEventListener {
private CallManager mCallManager;
private final PowerManager mPowerManager;
private final ConnectivityManager mConnectivityManager;
private BroadcastReceiver mHookReceiver;
private BroadcastReceiver mCallReceiver;
private TelephonyManager mTelephonyManager;
private PhoneStateListener mPhoneStateListener;
private WakeLock mProximityWakelock;
private final SensorManager mSensorManager;
private final Sensor mProximity;
@ -138,6 +136,27 @@ public class LinphoneManager implements SensorEventListener {
(ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
mSensorManager = (SensorManager) c.getSystemService(Context.SENSOR_SERVICE);
mProximity = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
mTelephonyManager = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE);
mPhoneStateListener =
new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.i("[Manager] Phone state is off hook");
setCallGsmON(true);
break;
case TelephonyManager.CALL_STATE_RINGING:
Log.i("[Manager] Phone state is ringing");
setCallGsmON(true);
break;
case TelephonyManager.CALL_STATE_IDLE:
Log.i("[Manager] Phone state is idle");
setCallGsmON(false);
break;
}
}
};
mHasLastCallSasBeenRejected = false;
mCallManager = new CallManager(c);
@ -194,8 +213,7 @@ public class LinphoneManager implements SensorEventListener {
if (state == State.IncomingReceived
&& !call.equals(core.getCurrentCall())) {
if (call.getReplacedCall() != null) {
// attended transfer
// it will be accepted automatically.
// attended transfer will be accepted automatically.
return;
}
}
@ -222,15 +240,16 @@ public class LinphoneManager implements SensorEventListener {
};
mTimer = new Timer("Auto answer");
mTimer.schedule(lTask, mPrefs.getAutoAnswerTime());
}
if (state == State.End || state == State.Error) {
} else if (state == State.End || state == State.Error) {
if (mCore.getCallsNb() == 0) {
// Disabling proximity sensor
enableProximitySensing(false);
Log.i("[Manager] Unregistering phone state listener");
mTelephonyManager.listen(
mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
}
}
if (state == State.UpdatedByRemote) {
} else if (state == State.UpdatedByRemote) {
// If the correspondent proposes video while audio call
boolean remoteVideo = call.getRemoteParams().videoEnabled();
boolean localVideo = call.getCurrentParams().videoEnabled();
@ -243,6 +262,12 @@ public class LinphoneManager implements SensorEventListener {
&& mCore.getConference() == null) {
call.deferUpdate();
}
} else if (state == State.Connected) {
if (core.getCallsNb() == 1) {
Log.i("[Manager] Registering phone state listener");
mTelephonyManager.listen(
mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
}
@ -414,17 +439,6 @@ public class LinphoneManager implements SensorEventListener {
} catch (RuntimeException e) {
Log.e("[Manager] Destroy Core Runtime Exception: " + e);
} finally {
try {
mContext.unregisterReceiver(mHookReceiver);
} catch (Exception e) {
Log.e("[Manager] unregister receiver exception: " + e);
}
try {
mContext.unregisterReceiver(mCallReceiver);
} catch (Exception e) {
Log.e("[Manager] unregister receiver exception: " + e);
}
mCore = null;
}
}
@ -529,25 +543,11 @@ public class LinphoneManager implements SensorEventListener {
PushNotificationUtils.init(mContext);
}
IntentFilter mCallIntentFilter =
new IntentFilter("android.intent.action.ACTION_NEW_OUTGOING_CALL");
mCallIntentFilter.setPriority(99999999);
mCallReceiver = new OutgoingCallReceiver();
try {
mContext.registerReceiver(mCallReceiver, mCallIntentFilter);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
mProximityWakelock =
mPowerManager.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
mContext.getPackageName() + ";manager_proximity_sensor");
IntentFilter mHookIntentFilter = new IntentFilter("com.base.module.phone.HOOKEVENT");
mHookIntentFilter.setPriority(999);
mHookReceiver = new HookReceiver();
mContext.registerReceiver(mHookReceiver, mHookIntentFilter);
resetCameraFromPreferences();
mAccountCreator = mCore.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
@ -886,6 +886,9 @@ public class LinphoneManager implements SensorEventListener {
public void setCallGsmON(boolean on) {
mCallGsmON = on;
if (on) {
mCore.pauseAllCalls();
}
}
private String getString(int key) {

View file

@ -45,7 +45,6 @@ import org.linphone.core.LoggingServiceListener;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
import org.linphone.notifications.NotificationsManager;
import org.linphone.receivers.BluetoothManager;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.ActivityMonitor;
import org.linphone.utils.DeviceOrientationEventListener;
@ -103,7 +102,6 @@ public final class LinphoneService extends Service {
private NotificationsManager mNotificationManager;
private LinphoneManager mLinphoneManager;
private ContactsManager mContactsManager;
private BluetoothManager mBluetoothManager;
private DeviceOrientationEventListener mOrientationHelper;
private Class<? extends Activity> 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();

View file

@ -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;

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);

File diff suppressed because it is too large Load diff

View file

@ -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();
}

View file

@ -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();
}
}
}

View file

@ -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);

View file

@ -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());
}
}
}

View file

@ -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();

View file

@ -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<String, String> mEncoderTexts;
private HashMap<String, String> 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("<b>" + name + " </b>" + 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);
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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(

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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<BluetoothDevice> 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);
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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(

View file

@ -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<BluetoothDevice> 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));
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -4,6 +4,10 @@
<bitmap android:src="@drawable/route_bluetooth_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_selected="true">
<bitmap android:src="@drawable/route_bluetooth_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_enabled="false">
<bitmap android:src="@drawable/route_bluetooth_default"
android:tint="?attr/drawableTintDisabledColor"/>

View file

@ -4,6 +4,10 @@
<bitmap android:src="@drawable/route_earpiece_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_selected="true">
<bitmap android:src="@drawable/route_earpiece_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_enabled="false">
<bitmap android:src="@drawable/route_earpiece_default"
android:tint="?attr/drawableTintDisabledColor"/>

View file

@ -4,6 +4,10 @@
<bitmap android:src="@drawable/route_speaker_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_selected="true">
<bitmap android:src="@drawable/route_speaker_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_enabled="false">
<bitmap android:src="@drawable/route_speaker_default"
android:tint="?attr/drawableTintDisabledColor"/>

View file

@ -4,6 +4,10 @@
<bitmap android:src="@drawable/routes_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_selected="true">
<bitmap android:src="@drawable/routes_default"
android:tint="?attr/drawableTintOverColor"/>
</item>
<item android:state_enabled="false">
<bitmap android:src="@drawable/routes_default"
android:tint="?attr/drawableTintDisabledColor"/>

View file

@ -8,21 +8,19 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
<GridLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@color/dark_grey_color"
android:baselineAligned="false"
android:padding="20dp"
android:orientation="horizontal">
android:columnCount="2"
android:padding="20dp">
<LinearLayout
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
@ -53,10 +51,9 @@
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
@ -75,9 +72,10 @@
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/about_liblinphone_sdk_version" />
</LinearLayout>
</LinearLayout>
</GridLayout>
<LinearLayout
android:layout_width="match_parent"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -10,66 +10,37 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/fragmentContainer"
<TextureView
android:id="@+id/remote_video_texture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
android:layout_height="match_parent" />
<TextureView
android:id="@+id/local_preview_texture"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" />
<RelativeLayout
android:id="@+id/active_calls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:layout_marginTop="40dp">
<LinearLayout
android:id="@+id/conference_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_gravity="top"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout>
<include layout="@layout/call_conference_header" android:visibility="gone" />
<RelativeLayout
android:id="@+id/active_call"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/active_call_info"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:alpha="0.8"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<TextView
android:id="@+id/current_contact_name"
style="@style/big_contact_name_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingTop="5dp" />
<Chronometer
android:id="@+id/current_call_timer"
style="@style/sip_uri_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
remote_pause
</LinearLayout>
<include layout="@layout/call_active_header" />
<RelativeLayout
android:layout_width="match_parent"
@ -83,30 +54,7 @@
</RelativeLayout>
<LinearLayout
android:id="@+id/remote_pause"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.9"
android:background="@color/dark_grey_color"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/waiting_time" />
<TextView
style="@style/call_status_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/call_paused_by_remote" />
</LinearLayout>
<include layout="@layout/call_paused_by_remote" android:visibility="gone" />
<ImageView
android:id="@+id/pause"
@ -140,30 +88,6 @@
android:background="@color/white_color"
android:orientation="vertical"/>
<LinearLayout
android:id="@+id/no_current_call"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/calls_list"
android:background="@color/dark_grey_color"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/waiting_time" />
<TextView
style="@style/call_status_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/no_current_call" />
</LinearLayout>
<ImageView
android:id="@+id/recording"
android:layout_width="40dp"
@ -179,261 +103,45 @@
</RelativeLayout>
<LinearLayout
android:baselineAligned="false"
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/main_bar"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="0.5"
android:orientation="horizontal">
<ImageView
android:id="@+id/dialer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:background="@color/dark_grey_color"
android:contentDescription="@string/content_description_numpad"
android:padding="15dp"
android:src="@drawable/footer_dialer" />
<ImageView
android:id="@+id/hang_up"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0.5"
android:background="@drawable/hangup"
android:contentDescription="@string/content_description_hang_up"
android:padding="12dp"
android:src="@drawable/call_hangup" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:background="@drawable/footer_button">
<ImageView
android:id="@+id/chat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/dark_grey_color"
android:contentDescription="@string/content_description_chat_button"
android:padding="15dp"
android:src="@drawable/footer_chat" />
<TextView
android:id="@+id/missed_chats"
style="@style/unread_count_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/unread_message_count_bg"
android:gravity="center"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/secondary_bar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:gravity="bottom"
android:minHeight="60dp"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="0.25">
<ImageView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_switch_video"
android:padding="20dp"
android:src="@drawable/camera" />
<ProgressBar
android:id="@+id/video_in_progress"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="gone" />
</RelativeLayout>
<ImageView
android:id="@+id/micro"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="0.25"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_micro"
android:padding="10dp"
android:src="@drawable/micro" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25">
<ImageView
android:id="@+id/speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/speaker" />
<ImageView
android:id="@+id/audio_route"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_audio_route"
android:padding="15dp"
android:src="@drawable/routes"
android:visibility="gone" />
<ImageView
android:id="@+id/route_bluetooth"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/audio_route"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_bluetooth"
android:padding="15dp"
android:src="@drawable/route_bluetooth"
android:visibility="gone" />
<ImageView
android:id="@+id/route_earpiece"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_bluetooth"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_earpiece"
android:padding="15dp"
android:src="@drawable/route_earpiece"
android:visibility="gone" />
<ImageView
android:id="@+id/route_speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_earpiece"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/route_speaker"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25">
<ImageView
android:id="@+id/options"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_call_options"
android:padding="15dp"
android:src="@drawable/options" />
<ImageView
android:id="@+id/record_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/options"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_record_call"
android:padding="15dp"
android:src="@drawable/options_rec"
android:visibility="gone" />
<ImageView
android:id="@+id/add_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/record_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_add_call"
android:padding="15dp"
android:src="@drawable/options_add_call"
android:visibility="gone" />
<ImageView
android:id="@+id/transfer"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/add_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_transfer"
android:padding="15dp"
android:src="@drawable/options_transfer_call"
android:visibility="gone" />
<ImageView
android:id="@+id/conference"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/transfer"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_conference"
android:padding="15dp"
android:src="@drawable/options_start_conference"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:baselineAligned="false"
android:id="@+id/buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp">
android:gravity="bottom">
<org.linphone.views.Numpad
android:id="@+id/numpad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/toolbar_color"
android:contentDescription="@string/content_description_numpad"
android:visibility="gone" />
<!-- This is a better way of splitting screen 50/50 than using weights -->
<View
android:id="@+id/vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />
<include layout="@layout/call_primary_buttons"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@id/vertical_divider" />
<include layout="@layout/call_secondary_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/vertical_divider" />
</RelativeLayout>
</FrameLayout>
<org.linphone.views.Numpad
android:id="@+id/numpad"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/toolbar_color"
android:contentDescription="@string/content_description_numpad" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/side_menu_content"
@ -443,19 +151,22 @@
android:layout_marginTop="40dp"
android:background="@color/white_color">
<include
android:id="@+id/incall_stats"
layout="@layout/incall_stats" />
<fragment
android:id="@+id/call_stats_fragment"
android:name="org.linphone.call.CallStatsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/call_stats" />
</RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:id="@+id/status_bar_fragment"
android:name="org.linphone.call.CallStatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
</RelativeLayout>

View file

@ -6,10 +6,10 @@
<fragment
android:id="@+id/status_fragment"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="35dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/side_menu"

View file

@ -6,10 +6,10 @@
<fragment
android:id="@+id/status_fragment"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="35dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/side_menu"

View file

@ -6,10 +6,10 @@
<fragment
android:id="@+id/status_fragment"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/side_menu"

View file

@ -12,7 +12,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="?attr/backgroundContastColor"
android:gravity="center"
android:orientation="vertical"
@ -59,7 +58,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:orientation="vertical"
android:background="?attr/backgroundColor"
android:padding="10dp">

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"
@ -56,7 +56,7 @@
<TextView
android:id="@+id/answer_no"
android:text="No"
android:text="@string/no"
android:textAllCaps="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -72,7 +72,7 @@
<TextView
android:id="@+id/answer_yes"
android:text="Yes"
android:text="@string/yes"
android:textAllCaps="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<include
android:id="@+id/top_bar"

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="40dp"/>

View file

@ -10,66 +10,37 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/fragmentContainer"
<TextureView
android:id="@+id/remote_video_texture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
android:layout_height="match_parent"/>
<TextureView
android:id="@+id/local_preview_texture"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" />
<RelativeLayout
android:id="@+id/active_calls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="120dp"
android:layout_marginTop="40dp">
<LinearLayout
android:id="@+id/conference_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_gravity="top"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout>
<include layout="@layout/call_conference_header" android:visibility="gone" />
<RelativeLayout
android:id="@+id/active_call"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/active_call_info"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:alpha="0.8"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<TextView
android:id="@+id/current_contact_name"
style="@style/big_contact_name_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingTop="5dp" />
<Chronometer
android:id="@+id/current_call_timer"
style="@style/sip_uri_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
<include layout="@layout/call_active_header" />
<RelativeLayout
android:layout_width="match_parent"
@ -83,30 +54,7 @@
</RelativeLayout>
<LinearLayout
android:id="@+id/remote_pause"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.9"
android:background="@color/dark_grey_color"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/waiting_time" />
<TextView
style="@style/call_status_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/call_paused_by_remote" />
</LinearLayout>
<include layout="@layout/call_paused_by_remote" android:visibility="gone" />
<ImageView
android:id="@+id/pause"
@ -140,30 +88,6 @@
android:background="?attr/backgroundColor"
android:orientation="vertical"/>
<LinearLayout
android:id="@+id/no_current_call"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/calls_list"
android:background="?attr/backgroundColor"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/waiting_time" />
<TextView
style="@style/call_status_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/no_current_call" />
</LinearLayout>
<ImageView
android:id="@+id/recording"
android:layout_width="50dp"
@ -179,259 +103,37 @@
</RelativeLayout>
<LinearLayout
android:id="@+id/menu"
<RelativeLayout
android:id="@+id/buttons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/secondary_bar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="bottom"
android:minHeight="60dp"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="0.25">
<ImageView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_switch_video"
android:padding="20dp"
android:src="@drawable/camera" />
<ProgressBar
android:id="@+id/video_in_progress"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="gone" />
</RelativeLayout>
<ImageView
android:id="@+id/micro"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="0.25"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_micro"
android:padding="10dp"
android:src="@drawable/micro" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25">
<ImageView
android:id="@+id/speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/speaker" />
<ImageView
android:id="@+id/audio_route"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_audio_route"
android:padding="15dp"
android:src="@drawable/routes"
android:visibility="gone" />
<ImageView
android:id="@+id/route_bluetooth"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/audio_route"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_bluetooth"
android:padding="15dp"
android:src="@drawable/route_bluetooth"
android:visibility="gone" />
<ImageView
android:id="@+id/route_earpiece"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_bluetooth"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_earpiece"
android:padding="15dp"
android:src="@drawable/route_earpiece"
android:visibility="gone" />
<ImageView
android:id="@+id/route_speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_earpiece"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/route_speaker"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25">
<ImageView
android:id="@+id/options"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_call_options"
android:padding="15dp"
android:src="@drawable/options" />
<ImageView
android:id="@+id/record_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/options"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_record_call"
android:padding="15dp"
android:src="@drawable/options_rec"
android:visibility="gone" />
<ImageView
android:id="@+id/add_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/record_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_add_call"
android:padding="15dp"
android:src="@drawable/options_add_call"
android:visibility="gone" />
<ImageView
android:id="@+id/transfer"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/add_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_transfer"
android:padding="15dp"
android:src="@drawable/options_transfer_call"
android:visibility="gone" />
<ImageView
android:id="@+id/conference"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/transfer"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_conference"
android:padding="15dp"
android:src="@drawable/options_start_conference"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/main_bar"
<include layout="@layout/call_primary_buttons"
android:id="@+id/call_primary_buttons"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="center"
android:orientation="horizontal">
android:layout_alignParentBottom="true" />
<ImageView
android:id="@+id/dialer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:background="@color/dark_grey_color"
android:contentDescription="@string/content_description_numpad"
android:padding="15dp"
android:src="@drawable/footer_dialer" />
<ImageView
android:id="@+id/hang_up"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0.5"
android:background="@drawable/hangup"
android:contentDescription="@string/content_description_hang_up"
android:padding="12dp"
android:src="@drawable/call_hangup" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:background="@drawable/footer_button">
<ImageView
android:id="@+id/chat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/dark_grey_color"
android:contentDescription="@string/content_description_chat_button"
android:padding="15dp"
android:src="@drawable/footer_chat" />
<TextView
android:id="@+id/missed_chats"
style="@style/unread_count_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/unread_message_count_bg"
android:gravity="center"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="120dp">
<org.linphone.views.Numpad
android:id="@+id/numpad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/toolbar_color"
android:contentDescription="@string/content_description_numpad"
android:visibility="gone" />
<include layout="@layout/call_secondary_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/call_primary_buttons" />
</RelativeLayout>
</FrameLayout>
<org.linphone.views.Numpad
android:id="@+id/numpad"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/toolbar_color"
android:contentDescription="@string/content_description_numpad" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/side_menu_content"
@ -441,19 +143,22 @@
android:layout_marginTop="40dp"
android:background="?attr/backgroundColor">
<include
android:id="@+id/incall_stats"
layout="@layout/incall_stats" />
<fragment
android:id="@+id/call_stats_fragment"
android:name="org.linphone.call.CallStatsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/call_stats" />
</RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:id="@+id/status_bar_fragment"
android:name="org.linphone.call.CallStatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
</RelativeLayout>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/active_call_info"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:alpha="0.8"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<TextView
android:id="@+id/current_contact_name"
style="@style/big_contact_name_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingTop="5dp" />
<Chronometer
android:id="@+id/current_call_timer"
style="@style/sip_uri_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_margin="10dp">
<RelativeLayout
android:id="@+id/avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center">
<include layout="@layout/contact_avatar" />
</RelativeLayout>
<LinearLayout
android:id="@+id/contact"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/contact_name"
style="@style/contact_name_list_cell_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:maxLines="1" />
<Chronometer
android:id="@+id/call_timer"
style="@style/call_timer_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|right" />
</LinearLayout>
<ImageView
android:id="@+id/remove_from_conference"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_exit_conference"
android:gravity="center_vertical"
android:src="@drawable/conference_exit_default" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerColor" />
</LinearLayout>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conference_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<RelativeLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentTop="true">
<TextView
style="@style/header_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="10dp"
android:text="@string/conference" />
<ImageView
android:id="@+id/conference_pause"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_pause"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/pause" />
</RelativeLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/header"
android:background="?attr/dividerColor" />
<LinearLayout
android:id="@+id/conference_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/divider"
android:layout_gravity="top"
android:orientation="vertical" />
</RelativeLayout>

View file

@ -2,7 +2,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="2dp"
android:alpha="0.5"
android:background="?attr/accentColor"
android:gravity="center_vertical"
@ -10,13 +9,11 @@
android:padding="5dp">
<ImageView
android:id="@+id/contact_picture"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/conference_start" />
<TextView
android:id="@+id/contact_name"
style="@style/call_contact_name_paused_font"
android:layout_width="0dip"
android:layout_height="wrap_content"
@ -28,7 +25,7 @@
android:text="@string/conference" />
<ImageView
android:id="@+id/conference_pause"
android:id="@+id/conference_resume"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/content_description_pause"

View file

@ -2,7 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="?attr/accentColorLight30"
android:alpha="0.5"
android:background="?attr/accentColor"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp">

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.call.CallStatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<RelativeLayout
android:id="@+id/top_bar"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.call.CallStatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<RelativeLayout
android:id="@+id/top_bar"

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/remote_pause"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.9"
android:background="@color/dark_grey_color"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/waiting_time" />
<TextView
style="@style/call_status_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/call_paused_by_remote" />
</LinearLayout>

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_bar"
android:layout_width="match_parent"
android:layout_height="60dp">
<View
android:id="@+id/vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/hang_up"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/hangup"
android:contentDescription="@string/content_description_hang_up"
android:padding="12dp"
android:src="@drawable/call_hangup" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/vertical_divider">
<View
android:id="@+id/left_vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"/>
<ImageView
android:id="@+id/dialer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/left_vertical_divider"
android:background="@drawable/button_background_dark"
android:contentDescription="@string/content_description_numpad"
android:padding="15dp"
android:src="@drawable/footer_dialer" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/vertical_divider">
<View
android:id="@+id/right_vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/right_vertical_divider"
android:background="@drawable/footer_button">
<ImageView
android:id="@+id/chat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/button_background_dark"
android:contentDescription="@string/content_description_chat_button"
android:padding="15dp"
android:src="@drawable/footer_chat" />
<TextView
android:id="@+id/missed_chats"
style="@style/unread_count_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/unread_message_count_bg"
android:gravity="center"
android:visibility="gone" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>

View file

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/secondary_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:minHeight="60dp">
<View
android:id="@+id/vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/vertical_divider">
<View
android:id="@+id/left_vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@id/left_vertical_divider">
<ImageView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_switch_video"
android:padding="20dp"
android:src="@drawable/camera" />
<ProgressBar
android:id="@+id/video_in_progress"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="gone" />
</RelativeLayout>
<ImageView
android:id="@+id/micro"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_toRightOf="@id/left_vertical_divider"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_micro"
android:padding="10dp"
android:src="@drawable/micro" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/vertical_divider">
<View
android:id="@+id/right_vertical_divider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/right_vertical_divider">
<ImageView
android:id="@+id/speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/speaker" />
<ImageView
android:id="@+id/audio_route"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_audio_route"
android:padding="15dp"
android:src="@drawable/routes"
android:visibility="gone" />
<ImageView
android:id="@+id/route_bluetooth"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/audio_route"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_bluetooth"
android:padding="15dp"
android:src="@drawable/route_bluetooth"
android:visibility="gone" />
<ImageView
android:id="@+id/route_earpiece"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_bluetooth"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_earpiece"
android:padding="15dp"
android:src="@drawable/route_earpiece"
android:visibility="gone" />
<ImageView
android:id="@+id/route_speaker"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/route_earpiece"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_toggle_speaker"
android:padding="15dp"
android:src="@drawable/route_speaker"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/right_vertical_divider">
<ImageView
android:id="@+id/options"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_call_options"
android:padding="15dp"
android:src="@drawable/options" />
<ImageView
android:id="@+id/record_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/options"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_record_call"
android:padding="15dp"
android:src="@drawable/options_rec"
android:visibility="gone" />
<ImageView
android:id="@+id/add_call"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/record_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_add_call"
android:padding="15dp"
android:src="@drawable/options_add_call"
android:visibility="gone" />
<ImageView
android:id="@+id/transfer"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/add_call"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_transfer"
android:padding="15dp"
android:src="@drawable/options_transfer_call"
android:visibility="gone" />
<ImageView
android:id="@+id/conference"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/transfer"
android:background="?attr/button_background_drawable"
android:contentDescription="@string/content_description_conference"
android:padding="15dp"
android:src="@drawable/options_start_conference"
android:visibility="gone" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="?attr/accentColor"
android:orientation="horizontal">
<ImageView
android:id="@+id/call_quality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:contentDescription="@string/content_description_call_quality"
android:padding="10dp"
android:src="@drawable/call_quality_indicator_0" />
<ImageView
android:id="@+id/status_led"
android:layout_width="20dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_toRightOf="@id/call_quality"
android:adjustViewBounds="true"
android:paddingLeft="5dp"
android:src="@drawable/led_disconnected" />
<TextView
android:id="@+id/status_text"
style="@style/status_bar_font"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/status_led"
android:gravity="center_vertical"
android:paddingLeft="5dp"
android:text="@string/status_not_connected" />
<ImageView
android:id="@+id/encryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_encryption"
android:padding="5dp"
android:src="@drawable/security_ko"
android:visibility="gone" />
</RelativeLayout>

View file

@ -102,7 +102,7 @@
android:src="@drawable/delete" />
</LinearLayout>
<include layout="@layout/edit_list" />
<include layout="@layout/edit_list" android:visibility="gone" />
</RelativeLayout>

View file

@ -63,7 +63,7 @@
android:src="@drawable/delete" />
</LinearLayout>
<include layout="@layout/edit_list" />
<include layout="@layout/edit_list" android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chatList"

View file

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_margin="2dp"
android:divider="?attr/dividerColor"
android:dividerHeight="1dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="2dp">
<ImageView
android:id="@+id/contactPicture"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/avatar" />
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/contactNameOrNumber"
style="@style/contact_name_list_cell_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:maxLines="1"
android:paddingLeft="20dp"
android:paddingRight="10dp" />
<Chronometer
android:id="@+id/call_timer"
style="@style/call_timer_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|right"
android:paddingLeft="10dp"
android:paddingRight="20dp" />
</LinearLayout>
<ImageView
android:id="@+id/quitConference"
android:layout_width="30dp"
android:layout_height="30dp"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_exit_conference"
android:gravity="center_vertical"
android:scaleType="fitCenter"
android:src="@drawable/conference_exit_default" />
</LinearLayout>

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="10dp">
<TextView
style="@style/header_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/conference" />
<Chronometer
android:id="@+id/callTimer"
style="@style/call_timer_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
</LinearLayout>
<ImageView
android:id="@+id/conference_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_pause"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/pause" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="?attr/dividerColor" />
</RelativeLayout>

View file

@ -85,7 +85,7 @@
</LinearLayout>
<include layout="@layout/edit_list" />
<include layout="@layout/edit_list" android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"

View file

@ -4,8 +4,7 @@
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="?attr/lighToolbarBackgroundColor"
android:orientation="horizontal"
android:visibility="gone">
android:orientation="horizontal">
<ImageView
android:id="@+id/cancel"

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>

View file

@ -79,7 +79,7 @@
</LinearLayout>
<include layout="@layout/edit_list" />
<include layout="@layout/edit_list" android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/history_list"

View file

@ -7,10 +7,10 @@
<fragment
android:id="@+id/status"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<LinearLayout
android:id="@+id/topbar"

View file

@ -6,10 +6,10 @@
<fragment
android:id="@+id/status_fragment"
android:name="org.linphone.fragments.StatusFragment"
android:name="org.linphone.fragments.StatusBarFragment"
android:layout_width="match_parent"
android:layout_height="40dp"
tools:layout="@layout/status" />
tools:layout="@layout/status_bar" />
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/side_menu"

View file

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
style="@style/header_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:text="@string/password" />
<EditText
android:id="@+id/password1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="20dp"
android:background="@drawable/resizable_textfield"
android:hint="@string/password"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/black_color"
android:textCursorDrawable="@null" />
<TextView
android:id="@+id/customText"
style="@style/header_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:text="@string/confirm_password" />
<EditText
android:id="@+id/password2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="20dp"
android:background="@drawable/resizable_textfield"
android:hint="@string/confirm_password"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/black_color"
android:textCursorDrawable="@null" />
</LinearLayout>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/led"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:src="@drawable/led_disconnected" />

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@color/transparent_color"
android:drawSelectorOnTop="false"
android:scrollbarAlwaysDrawVerticalTrack="true" />

View file

@ -44,7 +44,7 @@
android:src="@drawable/delete" />
</LinearLayout>
<include layout="@layout/edit_list" />
<include layout="@layout/edit_list" android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:scaleType="center"
android:src="@drawable/linphone_logo_orange" />
<ProgressBar
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:visibility="gone" />
</RelativeLayout>

View file

@ -16,17 +16,6 @@
android:padding="10dp"
android:src="@drawable/menu" />
<ImageView
android:id="@+id/call_quality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:contentDescription="@string/content_description_call_quality"
android:padding="10dp"
android:src="@drawable/call_quality_indicator_0"
android:visibility="invisible" />
<ImageView
android:id="@+id/status_led"
android:layout_width="20dp"
@ -68,16 +57,4 @@
android:paddingRight="10dp"
android:visibility="gone" />
<ImageView
android:id="@+id/encryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_encryption"
android:padding="5dp"
android:src="@drawable/security_ko"
android:visibility="gone" />
</RelativeLayout>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toastRoot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/backgroundContastColor"
android:orientation="vertical">
<TextView
android:id="@+id/toastMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/secondaryTextColor" />
</LinearLayout>

View file

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/carddav_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Username"
android:text="sylvain" />
<EditText
android:id="@+id/carddav_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Pwd" />
<EditText
android:id="@+id/carddav_ha1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="HA1"
android:text="4747ce2517a985f2fc20234a38f068b6" />
</LinearLayout>
<EditText
android:id="@+id/carddav_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Server URL"
android:text="http://192.168.0.230/sabredav/addressbookserver.php/addressbooks/sylvain/default" />
<Button
android:id="@+id/carddav_synchronize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Synchronize" />
<TextView
android:id="@+id/carddav_events"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show more