Merge branch 'release/4.2'

This commit is contained in:
Sylvain Berfini 2019-12-09 16:45:19 +01:00
commit 3c93974534
122 changed files with 1823 additions and 1092 deletions

View file

@ -8,3 +8,4 @@ type = ANDROID
file_filter = app/src/main/res/values-<lang>/strings.xml file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = app/src/main/res/values/strings.xml source_file = app/src/main/res/values/strings.xml
source_lang = en source_lang = en

View file

@ -12,6 +12,14 @@ Group changes to describe their impact on the project, as follows:
## [Unreleased] ## [Unreleased]
### Added
-
### Changed
-
## [4.2.0] - 2019-12-09
### Added ### Added
- Added shortcuts to contacts' latest chat rooms - Added shortcuts to contacts' latest chat rooms
- Improved device's do not disturb policy compliance - Improved device's do not disturb policy compliance
@ -22,8 +30,10 @@ Group changes to describe their impact on the project, as follows:
- Using new AAudio & Camera2 frameworks for better performances (if available) - Using new AAudio & Camera2 frameworks for better performances (if available)
- Android 10 compatibility - Android 10 compatibility
- New plugin loader to be compatible with app bundle distribution mode - New plugin loader to be compatible with app bundle distribution mode
- Restart service if foreground service setting is on when app is updated
- Change bluetooth volume while in call if BT device connected and used
## Changed ### Changed
- Improved performances to reduce startup time - Improved performances to reduce startup time
- Call statistics are now available for each call & conference - Call statistics are now available for each call & conference
- Added our own devices in LIME encrypted chatrooms' security view - Added our own devices in LIME encrypted chatrooms' security view

View file

@ -1,6 +1,34 @@
[![pipeline status](https://gitlab.linphone.org/BC/public/linphone-android/badges/master/pipeline.svg)](https://gitlab.linphone.org/BC/public/linphone-android/commits/master) [![pipeline status](https://gitlab.linphone.org/BC/public/linphone-android/badges/master/pipeline.svg)](https://gitlab.linphone.org/BC/public/linphone-android/commits/master)
Linphone is a free VoIP and video softphone based on the SIP protocol.
Linphone is an open source softphone for voice and video over IP calling and instant messaging.
It is fully SIP-based, for all calling, presence and IM features.
General description is available from [linphone web site](https://www.linphone.org/technical-corner/linphone)
### License
Copyright © Belledonne Communications
Linphone is dual licensed, and is available either :
- under a [GNU/GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html), for free (open source). Please make sure that you
understand and agree with the terms of this license before using it (see LICENSE file for
details).
- under a proprietary license, for a fee, to be used in closed source applications. Contact
[Belledonne Communications](https://www.linphone.org/contact) for any question about costs and services.
### Documentation
- Supported features and RFCs : https://www.linphone.org/technical-corner/linphone/features
- Linphone public wiki : https://wiki.linphone.org/xwiki/wiki/public/view/Linphone/
# What's new # What's new

View file

@ -73,8 +73,7 @@ excludePackage.add('**/LICENSE.txt')
repositories { repositories {
maven { maven {
// Replace snapshots by releases for releases ! url "https://linphone.org/maven_repository"
url "https://linphone.org/snapshots/maven_repository"
} }
} }
@ -89,7 +88,7 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 4129 versionCode 4213
versionName "${project.version}" versionName "${project.version}"
applicationId getPackageName() applicationId getPackageName()
multiDexEnabled true multiDexEnabled true

View file

@ -64,7 +64,8 @@
android:largeHeap="true" android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:resizeableActivity="true" android:resizeableActivity="true"
android:theme="@style/LinphoneStyle"> android:theme="@style/LinphoneStyle"
android:requestLegacyExternalStorage="true">
<!-- Starting activities --> <!-- Starting activities -->
@ -81,7 +82,7 @@
<!-- Main activities --> <!-- Main activities -->
<activity <activity
android:name=".activities.DialerActivity" android:name=".dialer.DialerActivity"
android:launchMode="singleTop"> android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.DIAL" /> <action android:name="android.intent.action.DIAL" />
@ -211,7 +212,7 @@
<!-- Services --> <!-- Services -->
<service <service
android:name=".LinphoneService" android:name=".service.LinphoneService"
android:label="@string/service_name" /> android:label="@string/service_name" />
<service <service
@ -252,9 +253,8 @@
<receiver android:name=".receivers.BootReceiver"> <receiver android:name=".receivers.BootReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" /> <action android:name="android.intent.action.ACTION_SHUTDOWN" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter> </intent-filter>
</receiver> </receiver>

View file

@ -21,28 +21,33 @@ package org.linphone;
import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.ACTION_MAIN;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import java.util.ArrayList;
import org.linphone.call.CallActivity; import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity; import org.linphone.call.CallIncomingActivity;
import org.linphone.call.CallOutgoingActivity; import org.linphone.call.CallOutgoingActivity;
import org.linphone.compatibility.Compatibility; import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel; import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService; import org.linphone.core.LoggingService;
import org.linphone.core.LoggingServiceListener; import org.linphone.core.LoggingServiceListener;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version; import org.linphone.mediastream.Version;
import org.linphone.notifications.NotificationsManager; import org.linphone.notifications.NotificationsManager;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.DeviceUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.PushNotificationUtils;
public class LinphoneContext { public class LinphoneContext {
private static LinphoneContext sInstance = null; private static LinphoneContext sInstance = null;
@ -78,7 +83,7 @@ public class LinphoneContext {
private NotificationsManager mNotificationManager; private NotificationsManager mNotificationManager;
private LinphoneManager mLinphoneManager; private LinphoneManager mLinphoneManager;
private ContactsManager mContactsManager; private ContactsManager mContactsManager;
private Class<? extends Activity> mIncomingReceivedActivity = CallIncomingActivity.class; private final ArrayList<CoreStartedListener> mCoreStartedListeners;
public static boolean isReady() { public static boolean isReady() {
return sInstance != null; return sInstance != null;
@ -90,6 +95,7 @@ public class LinphoneContext {
public LinphoneContext(Context context) { public LinphoneContext(Context context) {
mContext = context; mContext = context;
mCoreStartedListeners = new ArrayList<>();
LinphonePreferences.instance().setContext(context); LinphonePreferences.instance().setContext(context);
Factory.instance().setLogCollectionPath(context.getFilesDir().getAbsolutePath()); Factory.instance().setLogCollectionPath(context.getFilesDir().getAbsolutePath());
@ -100,23 +106,40 @@ public class LinphoneContext {
dumpDeviceInformation(); dumpDeviceInformation();
dumpLinphoneInformation(); dumpLinphoneInformation();
String incomingReceivedActivityName =
LinphonePreferences.instance().getActivityToLaunchOnIncomingReceived();
try {
mIncomingReceivedActivity =
(Class<? extends Activity>) Class.forName(incomingReceivedActivityName);
} catch (ClassNotFoundException e) {
Log.e(e);
}
sInstance = this; sInstance = this;
Log.i("[Context] Ready"); Log.i("[Context] Ready");
mListener = mListener =
new CoreListenerStub() { new CoreListenerStub() {
@Override
public void onGlobalStateChanged(Core core, GlobalState state, String message) {
Log.i("[Context] Global state is [", state, "]");
if (state == GlobalState.On) {
for (CoreStartedListener listener : mCoreStartedListeners) {
listener.onCoreStarted();
}
}
}
@Override
public void onConfiguringStatus(
Core core, ConfiguringState status, String message) {
Log.i("[Context] Configuring state is [", status, "]");
if (status == ConfiguringState.Successful) {
LinphonePreferences.instance()
.setPushNotificationEnabled(
LinphonePreferences.instance()
.isPushNotificationEnabled());
}
}
@Override @Override
public void onCallStateChanged( public void onCallStateChanged(
Core core, Call call, Call.State state, String message) { Core core, Call call, Call.State state, String message) {
Log.i("[Context] Call state is [", state, "]");
if (mContext.getResources().getBoolean(R.bool.enable_call_notification)) { if (mContext.getResources().getBoolean(R.bool.enable_call_notification)) {
mNotificationManager.displayCallNotification(call); mNotificationManager.displayCallNotification(call);
} }
@ -157,12 +180,28 @@ public class LinphoneContext {
mLinphoneManager = new LinphoneManager(context); mLinphoneManager = new LinphoneManager(context);
mNotificationManager = new NotificationsManager(context); mNotificationManager = new NotificationsManager(context);
if (DeviceUtils.isAppUserRestricted(mContext)) {
// See https://firebase.google.com/docs/cloud-messaging/android/receive#restricted
Log.w(
"[Context] Device has been restricted by user (Android 9+), push notifications won't work !");
}
int bucket = DeviceUtils.getAppStandbyBucket(mContext);
if (bucket > 0) {
Log.w(
"[Context] Device is in bucket "
+ Compatibility.getAppStandbyBucketNameFromValue(bucket));
}
if (!PushNotificationUtils.isAvailable(mContext)) {
Log.w("[Context] Push notifications won't work !");
}
} }
public void start(boolean isPush) { public void start(boolean isPush) {
Log.i("[Context] Starting"); Log.i("[Context] Starting, push status is ", isPush);
mLinphoneManager.startLibLinphone(isPush); mLinphoneManager.startLibLinphone(isPush, mListener);
LinphoneManager.getCore().addListener(mListener);
mNotificationManager.onCoreReady(); mNotificationManager.onCoreReady();
@ -236,6 +275,14 @@ public class LinphoneContext {
return mContactsManager; return mContactsManager;
} }
public void addCoreStartedListener(CoreStartedListener listener) {
mCoreStartedListeners.add(listener);
}
public void removeCoreStartedListener(CoreStartedListener listener) {
mCoreStartedListeners.remove(listener);
}
/* Log device related information */ /* Log device related information */
private void dumpDeviceInformation() { private void dumpDeviceInformation() {
@ -266,7 +313,7 @@ public class LinphoneContext {
/* Call activities */ /* Call activities */
private void onIncomingReceived() { private void onIncomingReceived() {
Intent intent = new Intent().setClass(mContext, mIncomingReceivedActivity); Intent intent = new Intent(mContext, CallIncomingActivity.class);
// This flag is required to start an Activity from a Service context // This flag is required to start an Activity from a Service context
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent); mContext.startActivity(intent);
@ -285,4 +332,8 @@ public class LinphoneContext {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent); mContext.startActivity(intent);
} }
public interface CoreStartedListener {
void onCoreStarted();
}
} }

View file

@ -45,18 +45,18 @@ import java.util.Date;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import org.linphone.assistant.PhoneAccountLinkingAssistantActivity; import org.linphone.assistant.PhoneAccountLinkingAssistantActivity;
import org.linphone.call.AndroidAudioManager;
import org.linphone.call.CallManager; import org.linphone.call.CallManager;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.core.AccountCreator; import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub; import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Call.State; import org.linphone.core.Call.State;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListener;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.FriendList; import org.linphone.core.FriendList;
import org.linphone.core.GlobalState;
import org.linphone.core.PresenceActivity; import org.linphone.core.PresenceActivity;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
@ -68,7 +68,6 @@ import org.linphone.core.VersionUpdateCheckResult;
import org.linphone.core.tools.H264Helper; import org.linphone.core.tools.H264Helper;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.AndroidAudioManager;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.MediaScanner; import org.linphone.utils.MediaScanner;
import org.linphone.utils.PushNotificationUtils; import org.linphone.utils.PushNotificationUtils;
@ -160,37 +159,6 @@ public class LinphoneManager implements SensorEventListener {
mCoreListener = mCoreListener =
new CoreListenerStub() { new CoreListenerStub() {
@Override
public void onGlobalStateChanged(
final Core core, final GlobalState state, final String message) {
Log.i("New global state [", state, "]");
if (state == GlobalState.On) {
try {
initLiblinphone(core);
} catch (IllegalArgumentException iae) {
Log.e(
"[Manager] Global State Changed Illegal Argument Exception: "
+ iae);
}
}
}
@Override
public void onConfiguringStatus(
Core core, ConfiguringState state, String message) {
Log.d(
"[Manager] Remote provisioning status = "
+ state.toString()
+ " ("
+ message
+ ")");
LinphonePreferences prefs = LinphonePreferences.instance();
if (state == ConfiguringState.Successful) {
prefs.setPushNotificationEnabled(prefs.isPushNotificationEnabled());
}
}
@SuppressLint("Wakelock") @SuppressLint("Wakelock")
@Override @Override
public void onCallStateChanged( public void onCallStateChanged(
@ -198,7 +166,7 @@ public class LinphoneManager implements SensorEventListener {
final Call call, final Call call,
final State state, final State state,
final String message) { final String message) {
Log.i("[Manager] New call state [", state, "]"); Log.i("[Manager] Call state is [", state, "]");
if (state == State.IncomingReceived if (state == State.IncomingReceived
&& !call.equals(core.getCurrentCall())) { && !call.equals(core.getCurrentCall())) {
if (call.getReplacedCall() != null) { if (call.getReplacedCall() != null) {
@ -388,6 +356,7 @@ public class LinphoneManager implements SensorEventListener {
} }
public void restartCore() { public void restartCore() {
Log.w("[Manager] Restarting Core");
mCore.stop(); mCore.stop();
mCore.start(); mCore.start();
} }
@ -395,8 +364,8 @@ public class LinphoneManager implements SensorEventListener {
private void destroyCore() { private void destroyCore() {
Log.w("[Manager] Destroying Core"); Log.w("[Manager] Destroying Core");
if (LinphonePreferences.instance() != null) { if (LinphonePreferences.instance() != null) {
// We set network reachable at false before destroy LC to not send register with expires // We set network reachable at false before destroying the Core
// at 0 // to not send a register with expires at 0
if (LinphonePreferences.instance().isPushNotificationEnabled()) { if (LinphonePreferences.instance().isPushNotificationEnabled()) {
Log.w( Log.w(
"[Manager] Setting network reachability to False to prevent unregister and allow incoming push notifications"); "[Manager] Setting network reachability to False to prevent unregister and allow incoming push notifications");
@ -429,7 +398,7 @@ public class LinphoneManager implements SensorEventListener {
} }
} }
public synchronized void startLibLinphone(boolean isPush) { public synchronized void startLibLinphone(boolean isPush, CoreListener listener) {
try { try {
mCore = mCore =
Factory.instance() Factory.instance()
@ -437,6 +406,7 @@ public class LinphoneManager implements SensorEventListener {
mPrefs.getLinphoneDefaultConfig(), mPrefs.getLinphoneDefaultConfig(),
mPrefs.getLinphoneFactoryConfig(), mPrefs.getLinphoneFactoryConfig(),
mContext); mContext);
mCore.addListener(listener);
mCore.addListener(mCoreListener); mCore.addListener(mCoreListener);
if (isPush) { if (isPush) {
@ -466,6 +436,8 @@ public class LinphoneManager implements SensorEventListener {
/*use schedule instead of scheduleAtFixedRate to avoid iterate from being call in burst after cpu wake up*/ /*use schedule instead of scheduleAtFixedRate to avoid iterate from being call in burst after cpu wake up*/
mTimer = new Timer("Linphone scheduler"); mTimer = new Timer("Linphone scheduler");
mTimer.schedule(lTask, 0, 20); mTimer.schedule(lTask, 0, 20);
configureCore();
} catch (Exception e) { } catch (Exception e) {
Log.e(e, "[Manager] Cannot start linphone"); Log.e(e, "[Manager] Cannot start linphone");
} }
@ -474,8 +446,8 @@ public class LinphoneManager implements SensorEventListener {
H264Helper.setH264Mode(H264Helper.MODE_AUTO, mCore); H264Helper.setH264Mode(H264Helper.MODE_AUTO, mCore);
} }
private synchronized void initLiblinphone(Core core) { private synchronized void configureCore() {
mCore = core; Log.i("[Manager] Configuring Core");
mAudioManager = new AndroidAudioManager(mContext); mAudioManager = new AndroidAudioManager(mContext);
mCore.setZrtpSecretsFile(mBasePath + "/zrtp_secrets"); mCore.setZrtpSecretsFile(mBasePath + "/zrtp_secrets");
@ -544,6 +516,8 @@ public class LinphoneManager implements SensorEventListener {
mAccountCreator = mCore.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl()); mAccountCreator = mCore.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(mAccountCreatorListener); mAccountCreator.setListener(mAccountCreatorListener);
mCallGsmON = false; mCallGsmON = false;
Log.i("[Manager] Core configured");
} }
public void resetCameraFromPreferences() { public void resetCameraFromPreferences() {
@ -572,16 +546,28 @@ public class LinphoneManager implements SensorEventListener {
/* Account linking */ /* Account linking */
public AccountCreator getAccountCreator() {
if (mAccountCreator == null) {
Log.w("[Manager] Account creator shouldn't be null !");
mAccountCreator =
mCore.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(mAccountCreatorListener);
}
return mAccountCreator;
}
public void isAccountWithAlias() { public void isAccountWithAlias() {
if (mCore.getDefaultProxyConfig() != null) { if (mCore.getDefaultProxyConfig() != null) {
long now = new Timestamp(new Date().getTime()).getTime(); long now = new Timestamp(new Date().getTime()).getTime();
if (mAccountCreator != null && LinphonePreferences.instance().getLinkPopupTime() == null AccountCreator accountCreator = getAccountCreator();
if (LinphonePreferences.instance().getLinkPopupTime() == null
|| Long.parseLong(LinphonePreferences.instance().getLinkPopupTime()) < now) { || Long.parseLong(LinphonePreferences.instance().getLinkPopupTime()) < now) {
mAccountCreator.setUsername( accountCreator.reset();
accountCreator.setUsername(
LinphonePreferences.instance() LinphonePreferences.instance()
.getAccountUsername( .getAccountUsername(
LinphonePreferences.instance().getDefaultAccountIndex())); LinphonePreferences.instance().getDefaultAccountIndex()));
mAccountCreator.isAccountExist(); accountCreator.isAccountExist();
} }
} else { } else {
LinphonePreferences.instance().setLinkPopupTime(null); LinphonePreferences.instance().setLinkPopupTime(null);
@ -839,7 +825,7 @@ public class LinphoneManager implements SensorEventListener {
public void setCallGsmON(boolean on) { public void setCallGsmON(boolean on) {
mCallGsmON = on; mCallGsmON = on;
if (on) { if (on && mCore != null) {
mCore.pauseAllCalls(); mCore.pauseAllCalls();
} }
} }

View file

@ -24,9 +24,9 @@ import android.os.Bundle;
import android.view.Surface; import android.view.Surface;
import org.linphone.LinphoneContext; import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.service.LinphoneService;
public abstract class LinphoneGenericActivity extends ThemeableActivity { public abstract class LinphoneGenericActivity extends ThemeableActivity {
@Override @Override
@ -80,8 +80,15 @@ public abstract class LinphoneGenericActivity extends ThemeableActivity {
if (!LinphoneContext.isReady()) { if (!LinphoneContext.isReady()) {
new LinphoneContext(getApplicationContext()); new LinphoneContext(getApplicationContext());
LinphoneContext.instance().start(false); LinphoneContext.instance().start(false);
Log.i("[Generic Activity] Context created & started");
} }
Log.i("[Generic Activity] Starting Service");
try {
startService(new Intent().setClass(this, LinphoneService.class)); startService(new Intent().setClass(this, LinphoneService.class));
} catch (IllegalStateException ise) {
Log.e("[Generic Activity] Couldn't start service, exception: ", ise);
}
} }
} }
} }

View file

@ -24,15 +24,16 @@ import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.os.Bundle; import android.os.Bundle;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R; import org.linphone.R;
import org.linphone.assistant.MenuAssistantActivity; import org.linphone.assistant.MenuAssistantActivity;
import org.linphone.chat.ChatActivity; import org.linphone.chat.ChatActivity;
import org.linphone.contacts.ContactsActivity; import org.linphone.contacts.ContactsActivity;
import org.linphone.dialer.DialerActivity;
import org.linphone.history.HistoryActivity; import org.linphone.history.HistoryActivity;
import org.linphone.service.LinphoneService;
import org.linphone.service.ServiceWaitThread;
import org.linphone.service.ServiceWaitThreadListener;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.ServiceWaitThread;
import org.linphone.utils.ServiceWaitThreadListener;
/** Creates LinphoneService and wait until Core is ready to start main Activity */ /** Creates LinphoneService and wait until Core is ready to start main Activity */
public class LinphoneLauncherActivity extends Activity implements ServiceWaitThreadListener { public class LinphoneLauncherActivity extends Activity implements ServiceWaitThreadListener {

View file

@ -20,7 +20,6 @@
package org.linphone.activities; package org.linphone.activities;
import android.Manifest; import android.Manifest;
import android.app.ActivityManager;
import android.app.Dialog; import android.app.Dialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager; import android.app.FragmentManager;
@ -46,7 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import java.util.ArrayList; import java.util.ArrayList;
import org.linphone.LinphoneContext; import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R; import org.linphone.R;
import org.linphone.call.CallActivity; import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity; import org.linphone.call.CallIncomingActivity;
@ -66,6 +64,7 @@ import org.linphone.core.CoreListenerStub;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.core.RegistrationState; import org.linphone.core.RegistrationState;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.fragments.EmptyFragment; import org.linphone.fragments.EmptyFragment;
import org.linphone.fragments.StatusBarFragment; import org.linphone.fragments.StatusBarFragment;
import org.linphone.history.HistoryActivity; import org.linphone.history.HistoryActivity;
@ -74,7 +73,6 @@ import org.linphone.settings.LinphonePreferences;
import org.linphone.settings.SettingsActivity; import org.linphone.settings.SettingsActivity;
import org.linphone.utils.DeviceUtils; import org.linphone.utils.DeviceUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.PushNotificationUtils;
public abstract class MainActivity extends LinphoneGenericActivity public abstract class MainActivity extends LinphoneGenericActivity
implements StatusBarFragment.MenuClikedListener, SideMenuFragment.QuitClikedListener { implements StatusBarFragment.MenuClikedListener, SideMenuFragment.QuitClikedListener {
@ -276,23 +274,6 @@ public abstract class MainActivity extends LinphoneGenericActivity
super.onStart(); super.onStart();
requestRequiredPermissions(); requestRequiredPermissions();
if (DeviceUtils.isAppUserRestricted(this)) {
// See https://firebase.google.com/docs/cloud-messaging/android/receive#restricted
Log.w(
"[Main Activity] Device has been restricted by user (Android 9+), push notifications won't work !");
}
int bucket = DeviceUtils.getAppStandbyBucket(this);
if (bucket > 0) {
Log.w(
"[Main Activity] Device is in bucket "
+ Compatibility.getAppStandbyBucketNameFromValue(bucket));
}
if (!PushNotificationUtils.isAvailable(this)) {
Log.w("[Main Activity] Push notifications won't work !");
}
} }
@Override @Override
@ -443,10 +424,6 @@ public abstract class MainActivity extends LinphoneGenericActivity
private void quit() { private void quit() {
goHomeAndClearStack(); goHomeAndClearStack();
stopService(new Intent(Intent.ACTION_MAIN).setClass(this, LinphoneService.class));
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
am.killBackgroundProcesses(getString(R.string.sync_account_type));
android.os.Process.killProcess(android.os.Process.myPid());
} }
// Tab, Top and Status bars // Tab, Top and Status bars
@ -495,6 +472,14 @@ public abstract class MainActivity extends LinphoneGenericActivity
return granted == PackageManager.PERMISSION_GRANTED; return granted == PackageManager.PERMISSION_GRANTED;
} }
public boolean checkPermissions(String[] permissions) {
boolean allGranted = true;
for (String permission : permissions) {
allGranted &= checkPermission(permission);
}
return allGranted;
}
public void requestPermissionIfNotGranted(String permission) { public void requestPermissionIfNotGranted(String permission) {
if (!checkPermission(permission)) { if (!checkPermission(permission)) {
Log.i("[Permission] Requesting " + permission + " permission"); Log.i("[Permission] Requesting " + permission + " permission");

View file

@ -31,9 +31,11 @@ import android.widget.RelativeLayout;
import android.widget.Switch; import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.AccountCreator; import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub; import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Core;
import org.linphone.core.DialPlan; import org.linphone.core.DialPlan;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
@ -70,20 +72,26 @@ public class AccountConnectionAssistantActivity extends AssistantActivity {
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
mAccountCreator.setDomain(getString(R.string.default_domain)); AccountCreator accountCreator = getAccountCreator();
accountCreator.reset();
mConnect.setEnabled(false); mConnect.setEnabled(false);
if (mUsernameConnectionSwitch.isChecked()) { if (mUsernameConnectionSwitch.isChecked()) {
mAccountCreator.setUsername(mUsername.getText().toString()); accountCreator.setUsername(mUsername.getText().toString());
mAccountCreator.setPassword(mPassword.getText().toString()); accountCreator.setPassword(mPassword.getText().toString());
createProxyConfigAndLeaveAssistant(); createProxyConfigAndLeaveAssistant();
} else { } else {
mAccountCreator.setUsername(mPhoneNumber.getText().toString()); accountCreator.setPhoneNumber(
mPhoneNumber.getText().toString(),
mPrefix.getText().toString());
accountCreator.setUsername(accountCreator.getPhoneNumber());
AccountCreator.Status status = mAccountCreator.recoverAccount(); AccountCreator.Status status = accountCreator.recoverAccount();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Account Connection] recoverAccount returned " + status); Log.e(
"[Account Connection Assistant] recoverAccount returned "
+ status);
mConnect.setEnabled(true); mConnect.setEnabled(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -202,7 +210,9 @@ public class AccountConnectionAssistantActivity extends AssistantActivity {
@Override @Override
public void onRecoverAccount( public void onRecoverAccount(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Account Connection] onRecoverAccount status is " + status); Log.i(
"[Account Connection Assistant] onRecoverAccount status is "
+ status);
if (status.equals(AccountCreator.Status.RequestOk)) { if (status.equals(AccountCreator.Status.RequestOk)) {
Intent intent = Intent intent =
new Intent( new Intent(
@ -222,7 +232,12 @@ public class AccountConnectionAssistantActivity extends AssistantActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); Core core = LinphoneManager.getCore();
if (core != null) {
reloadLinphoneAccountCreatorConfig();
}
getAccountCreator().addListener(mListener);
DialPlan dp = getDialPlanForCurrentCountry(); DialPlan dp = getDialPlanForCurrentCountry();
displayDialPlan(dp); displayDialPlan(dp);
@ -236,7 +251,7 @@ public class AccountConnectionAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
} }
@Override @Override

View file

@ -21,16 +21,16 @@ package org.linphone.assistant;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import java.util.Locale;
import org.linphone.LinphoneContext; import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.DialerActivity;
import org.linphone.activities.LinphoneGenericActivity; import org.linphone.activities.LinphoneGenericActivity;
import org.linphone.core.AccountCreator; import org.linphone.core.AccountCreator;
import org.linphone.core.Core; import org.linphone.core.Core;
@ -38,29 +38,16 @@ import org.linphone.core.DialPlan;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
public abstract class AssistantActivity extends LinphoneGenericActivity public abstract class AssistantActivity extends LinphoneGenericActivity
implements CountryPicker.CountryPickedListener { implements CountryPicker.CountryPickedListener {
static AccountCreator mAccountCreator;
protected ImageView mBack; protected ImageView mBack;
private AlertDialog mCountryPickerDialog; private AlertDialog mCountryPickerDialog;
private CountryPicker mCountryPicker; private CountryPicker mCountryPicker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mAccountCreator == null) {
String url = LinphonePreferences.instance().getXmlrpcUrl();
Core core = LinphoneManager.getCore();
core.loadConfigFromXml(LinphonePreferences.instance().getDefaultDynamicConfigFile());
mAccountCreator = core.createAccountCreator(url);
}
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
@ -104,25 +91,78 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
} }
} }
void createProxyConfigAndLeaveAssistant() { @Override
Core core = LinphoneManager.getCore(); public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean useLinphoneDefaultValues = if (keyCode == KeyEvent.KEYCODE_BACK) {
getString(R.string.default_domain).equals(mAccountCreator.getDomain()); if (!mBack.isEnabled()) return true;
if (useLinphoneDefaultValues) { }
core.loadConfigFromXml(LinphonePreferences.instance().getLinphoneDynamicConfigFile()); return super.onKeyDown(keyCode, event);
} }
ProxyConfig proxyConfig = mAccountCreator.createProxyConfig(); public AccountCreator getAccountCreator() {
return LinphoneManager.getInstance().getAccountCreator();
}
private void reloadAccountCreatorConfig(String path) {
Core core = LinphoneManager.getCore();
if (core != null) {
core.loadConfigFromXml(path);
AccountCreator accountCreator = getAccountCreator();
accountCreator.reset();
accountCreator.setLanguage(Locale.getDefault().getLanguage());
}
}
void reloadDefaultAccountCreatorConfig() {
Log.i("[Assistant] Reloading configuration with default");
reloadAccountCreatorConfig(LinphonePreferences.instance().getDefaultDynamicConfigFile());
}
void reloadLinphoneAccountCreatorConfig() {
Log.i("[Assistant] Reloading configuration with specifics");
reloadAccountCreatorConfig(LinphonePreferences.instance().getLinphoneDynamicConfigFile());
}
void createProxyConfigAndLeaveAssistant() {
createProxyConfigAndLeaveAssistant(false);
}
void createProxyConfigAndLeaveAssistant(boolean isGenericAccount) {
Core core = LinphoneManager.getCore();
boolean useLinphoneDefaultValues =
getString(R.string.default_domain).equals(getAccountCreator().getDomain());
if (isGenericAccount) {
if (useLinphoneDefaultValues) {
Log.i(
"[Assistant] Default domain found for generic connection, reloading configuration");
core.loadConfigFromXml(
LinphonePreferences.instance().getLinphoneDynamicConfigFile());
} else {
Log.i("[Assistant] Third party domain found, keeping default values");
}
}
ProxyConfig proxyConfig = getAccountCreator().createProxyConfig();
if (isGenericAccount) {
if (useLinphoneDefaultValues) { if (useLinphoneDefaultValues) {
// Restore default values // Restore default values
core.loadConfigFromXml(LinphonePreferences.instance().getDefaultDynamicConfigFile()); Log.i("[Assistant] Restoring default assistant configuration");
core.loadConfigFromXml(
LinphonePreferences.instance().getDefaultDynamicConfigFile());
} else { } else {
// If this isn't a sip.linphone.org account, disable push notifications and enable // If this isn't a sip.linphone.org account, disable push notifications and enable
// service notification, otherwise incoming calls won't work (most probably) // service notification, otherwise incoming calls won't work (most probably)
if (proxyConfig != null) {
proxyConfig.setPushNotificationAllowed(false);
}
Log.w(
"[Assistant] Unknown domain used, push probably won't work, enable service mode");
LinphonePreferences.instance().setServiceNotificationVisibility(true); LinphonePreferences.instance().setServiceNotificationVisibility(true);
LinphoneContext.instance().getNotificationManager().startForeground(); LinphoneContext.instance().getNotificationManager().startForeground();
} }
}
if (proxyConfig == null) { if (proxyConfig == null) {
Log.e("[Assistant] Account creator couldn't create proxy config"); Log.e("[Assistant] Account creator couldn't create proxy config");
@ -206,6 +246,9 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
case PhoneNumberOverused: case PhoneNumberOverused:
message = getString(R.string.phone_number_overuse); message = getString(R.string.phone_number_overuse);
break; break;
case AccountNotExist:
message = getString(R.string.account_doesnt_exist);
break;
default: default:
message = getString(R.string.error_unknown); message = getString(R.string.error_unknown);
break; break;
@ -271,7 +314,7 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
} }
String phoneNumber = phoneNumberEditText.getText().toString(); String phoneNumber = phoneNumberEditText.getText().toString();
return mAccountCreator.setPhoneNumber(phoneNumber, prefix); return getAccountCreator().setPhoneNumber(phoneNumber, prefix);
} }
String getErrorFromPhoneNumberStatus(int status) { String getErrorFromPhoneNumberStatus(int status) {

View file

@ -30,9 +30,11 @@ import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.AccountCreator; import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub; import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Core;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
public class EmailAccountCreationAssistantActivity extends AssistantActivity { public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@ -65,7 +67,7 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
AccountCreator.UsernameStatus status = AccountCreator.UsernameStatus status =
mAccountCreator.setUsername(s.toString()); getAccountCreator().setUsername(s.toString());
mUsernameError.setVisibility( mUsernameError.setVisibility(
status == AccountCreator.UsernameStatus.Ok status == AccountCreator.UsernameStatus.Ok
? View.INVISIBLE ? View.INVISIBLE
@ -88,7 +90,7 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
AccountCreator.PasswordStatus status = AccountCreator.PasswordStatus status =
mAccountCreator.setPassword(s.toString()); getAccountCreator().setPassword(s.toString());
mPasswordError.setVisibility( mPasswordError.setVisibility(
status == AccountCreator.PasswordStatus.Ok status == AccountCreator.PasswordStatus.Ok
? View.INVISIBLE ? View.INVISIBLE
@ -146,7 +148,8 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
AccountCreator.EmailStatus status = mAccountCreator.setEmail(s.toString()); AccountCreator.EmailStatus status =
getAccountCreator().setEmail(s.toString());
mEmailError.setVisibility( mEmailError.setVisibility(
status == AccountCreator.EmailStatus.Ok status == AccountCreator.EmailStatus.Ok
? View.INVISIBLE ? View.INVISIBLE
@ -161,12 +164,13 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
enableButtonsAndFields(false); enableButtonsAndFields(false);
mAccountCreator.setDomain(getString(R.string.default_domain));
AccountCreator.Status status = mAccountCreator.isAccountExist(); AccountCreator.Status status = getAccountCreator().isAccountExist();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
enableButtonsAndFields(true); enableButtonsAndFields(true);
Log.e("[Email Account Creation] isAccountExists returned " + status); Log.e(
"[Email Account Creation Assistant] isAccountExists returned "
+ status);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
} }
@ -177,15 +181,19 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
new AccountCreatorListenerStub() { new AccountCreatorListenerStub() {
public void onIsAccountExist( public void onIsAccountExist(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Email Account Creation] onIsAccountExist status is " + status); Log.i(
"[Email Account Creation Assistant] onIsAccountExist status is "
+ status);
if (status.equals(AccountCreator.Status.AccountExist) if (status.equals(AccountCreator.Status.AccountExist)
|| status.equals(AccountCreator.Status.AccountExistWithAlias)) { || status.equals(AccountCreator.Status.AccountExistWithAlias)) {
showAccountAlreadyExistsDialog(); showAccountAlreadyExistsDialog();
enableButtonsAndFields(true); enableButtonsAndFields(true);
} else if (status.equals(AccountCreator.Status.AccountNotExist)) { } else if (status.equals(AccountCreator.Status.AccountNotExist)) {
status = mAccountCreator.createAccount(); status = getAccountCreator().createAccount();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Email Account Creation] createAccount returned " + status); Log.e(
"[Email Account Creation Assistant] createAccount returned "
+ status);
enableButtonsAndFields(true); enableButtonsAndFields(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -198,7 +206,9 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void onCreateAccount( public void onCreateAccount(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Email Account Creation] onCreateAccount status is " + status); Log.i(
"[Email Account Creation Assistant] onCreateAccount status is "
+ status);
if (status.equals(AccountCreator.Status.AccountCreated)) { if (status.equals(AccountCreator.Status.AccountCreated)) {
startActivity( startActivity(
new Intent( new Intent(
@ -235,7 +245,12 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); Core core = LinphoneManager.getCore();
if (core != null) {
reloadLinphoneAccountCreatorConfig();
}
getAccountCreator().addListener(mListener);
if (getResources().getBoolean(R.bool.pre_fill_email_in_assistant)) { if (getResources().getBoolean(R.bool.pre_fill_email_in_assistant)) {
Account[] accounts = AccountManager.get(this).getAccountsByType("com.google"); Account[] accounts = AccountManager.get(this).getAccountsByType("com.google");
@ -252,6 +267,6 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
} }
} }

View file

@ -41,7 +41,7 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
setContentView(R.layout.assistant_email_account_validation); setContentView(R.layout.assistant_email_account_validation);
TextView email = findViewById(R.id.send_email); TextView email = findViewById(R.id.send_email);
email.setText(mAccountCreator.getEmail()); email.setText(getAccountCreator().getEmail());
mFinishCreation = findViewById(R.id.assistant_check); mFinishCreation = findViewById(R.id.assistant_check);
mFinishCreation.setOnClickListener( mFinishCreation.setOnClickListener(
@ -50,9 +50,11 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
public void onClick(View v) { public void onClick(View v) {
mFinishCreation.setEnabled(false); mFinishCreation.setEnabled(false);
AccountCreator.Status status = mAccountCreator.isAccountActivated(); AccountCreator.Status status = getAccountCreator().isAccountActivated();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Email Account Validation] activateAccount returned " + status); Log.e(
"[Email Account Validation Assistant] activateAccount returned "
+ status);
mFinishCreation.setEnabled(true); mFinishCreation.setEnabled(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -65,7 +67,7 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
public void onIsAccountActivated( public void onIsAccountActivated(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i( Log.i(
"[Email Account Validation] onIsAccountActivated status is " "[Email Account Validation Assistant] onIsAccountActivated status is "
+ status); + status);
if (status.equals(AccountCreator.Status.AccountActivated)) { if (status.equals(AccountCreator.Status.AccountActivated)) {
createProxyConfigAndLeaveAssistant(); createProxyConfigAndLeaveAssistant();
@ -87,7 +89,7 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); getAccountCreator().addListener(mListener);
// Prevent user to go back, it won't be able to come back here after... // Prevent user to go back, it won't be able to come back here after...
mBack.setEnabled(false); mBack.setEnabled(false);
@ -96,6 +98,6 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
} }
} }

View file

@ -27,8 +27,12 @@ import android.widget.EditText;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.Core;
import org.linphone.core.TransportType; import org.linphone.core.TransportType;
import org.linphone.core.tools.Log;
public class GenericConnectionAssistantActivity extends AssistantActivity implements TextWatcher { public class GenericConnectionAssistantActivity extends AssistantActivity implements TextWatcher {
private TextView mLogin; private TextView mLogin;
@ -63,24 +67,31 @@ public class GenericConnectionAssistantActivity extends AssistantActivity implem
} }
private void configureAccount() { private void configureAccount() {
mAccountCreator.setUsername(mUsername.getText().toString()); Core core = LinphoneManager.getCore();
mAccountCreator.setDomain(mDomain.getText().toString()); if (core != null) {
mAccountCreator.setPassword(mPassword.getText().toString()); Log.i("[Generic Connection Assistant] Reloading configuration with default");
mAccountCreator.setDisplayName(mDisplayName.getText().toString()); reloadDefaultAccountCreatorConfig();
}
AccountCreator accountCreator = getAccountCreator();
accountCreator.setUsername(mUsername.getText().toString());
accountCreator.setDomain(mDomain.getText().toString());
accountCreator.setPassword(mPassword.getText().toString());
accountCreator.setDisplayName(mDisplayName.getText().toString());
switch (mTransport.getCheckedRadioButtonId()) { switch (mTransport.getCheckedRadioButtonId()) {
case R.id.transport_udp: case R.id.transport_udp:
mAccountCreator.setTransport(TransportType.Udp); accountCreator.setTransport(TransportType.Udp);
break; break;
case R.id.transport_tcp: case R.id.transport_tcp:
mAccountCreator.setTransport(TransportType.Tcp); accountCreator.setTransport(TransportType.Tcp);
break; break;
case R.id.transport_tls: case R.id.transport_tls:
mAccountCreator.setTransport(TransportType.Tls); accountCreator.setTransport(TransportType.Tls);
break; break;
} }
createProxyConfigAndLeaveAssistant(); createProxyConfigAndLeaveAssistant(true);
} }
@Override @Override

View file

@ -58,7 +58,7 @@ public class OpenH264DownloadAssistantActivity extends AssistantActivity {
public void onClick(View v) { public void onClick(View v) {
mYes.setEnabled(false); mYes.setEnabled(false);
mNo.setEnabled(false); mNo.setEnabled(false);
Log.e("[OpenH264 Downloader] Start download"); Log.e("[OpenH264 Downloader Assistant] Start download");
mProgress.setVisibility(View.VISIBLE); mProgress.setVisibility(View.VISIBLE);
mHelper.downloadCodec(); mHelper.downloadCodec();
} }
@ -71,7 +71,7 @@ public class OpenH264DownloadAssistantActivity extends AssistantActivity {
public void onClick(View v) { public void onClick(View v) {
mYes.setEnabled(false); mYes.setEnabled(false);
mNo.setEnabled(false); mNo.setEnabled(false);
Log.e("[OpenH264 Downloader] Download refused"); Log.e("[OpenH264 Downloader Assistant] Download refused");
goToLinphoneActivity(); goToLinphoneActivity();
} }
}); });
@ -96,7 +96,7 @@ public class OpenH264DownloadAssistantActivity extends AssistantActivity {
@Override @Override
public void OnError(String s) { public void OnError(String s) {
Log.e("[OpenH264 Downloader] " + s); Log.e("[OpenH264 Downloader Assistant] " + s);
mYes.setEnabled(true); mYes.setEnabled(true);
mNo.setEnabled(true); mNo.setEnabled(true);
} }

View file

@ -30,9 +30,11 @@ import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.AccountCreator; import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub; import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Core;
import org.linphone.core.DialPlan; import org.linphone.core.DialPlan;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
@ -67,18 +69,20 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
AccountCreator accountCreator = getAccountCreator();
enableButtonsAndFields(false); enableButtonsAndFields(false);
if (mUseUsernameInsteadOfPhoneNumber.isChecked()) { if (mUseUsernameInsteadOfPhoneNumber.isChecked()) {
mAccountCreator.setUsername(mUsername.getText().toString()); accountCreator.setUsername(mUsername.getText().toString());
} else { } else {
mAccountCreator.setUsername(mAccountCreator.getPhoneNumber()); accountCreator.setUsername(accountCreator.getPhoneNumber());
} }
mAccountCreator.setDomain(getString(R.string.default_domain));
AccountCreator.Status status = mAccountCreator.isAccountExist(); AccountCreator.Status status = accountCreator.isAccountExist();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Phone Account Creation] isAccountExists returned " + status); Log.e(
"[Phone Account Creation Assistant] isAccountExists returned "
+ status);
enableButtonsAndFields(true); enableButtonsAndFields(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -167,15 +171,19 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
new AccountCreatorListenerStub() { new AccountCreatorListenerStub() {
public void onIsAccountExist( public void onIsAccountExist(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Phone Account Creation] onIsAccountExist status is " + status); Log.i(
"[Phone Account Creation Assistant] onIsAccountExist status is "
+ status);
if (status.equals(AccountCreator.Status.AccountExist) if (status.equals(AccountCreator.Status.AccountExist)
|| status.equals(AccountCreator.Status.AccountExistWithAlias)) { || status.equals(AccountCreator.Status.AccountExistWithAlias)) {
showAccountAlreadyExistsDialog(); showAccountAlreadyExistsDialog();
enableButtonsAndFields(true); enableButtonsAndFields(true);
} else if (status.equals(AccountCreator.Status.AccountNotExist)) { } else if (status.equals(AccountCreator.Status.AccountNotExist)) {
status = mAccountCreator.createAccount(); status = getAccountCreator().createAccount();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Phone Account Creation] createAccount returned " + status); Log.e(
"[Phone Account Creation Assistant] createAccount returned "
+ status);
enableButtonsAndFields(true); enableButtonsAndFields(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -188,7 +196,9 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
public void onCreateAccount( public void onCreateAccount(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Phone Account Creation] onCreateAccount status is " + status); Log.i(
"[Phone Account Creation Assistant] onCreateAccount status is "
+ status);
if (status.equals(AccountCreator.Status.AccountCreated)) { if (status.equals(AccountCreator.Status.AccountCreated)) {
startActivity( startActivity(
new Intent( new Intent(
@ -206,7 +216,12 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); Core core = LinphoneManager.getCore();
if (core != null) {
reloadLinphoneAccountCreatorConfig();
}
getAccountCreator().addListener(mListener);
DialPlan dp = getDialPlanForCurrentCountry(); DialPlan dp = getDialPlanForCurrentCountry();
displayDialPlan(dp); displayDialPlan(dp);
@ -220,7 +235,7 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
} }
@Override @Override
@ -247,7 +262,7 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
if (status == AccountCreator.PhoneNumberStatus.Ok.toInt()) { if (status == AccountCreator.PhoneNumberStatus.Ok.toInt()) {
if (mUseUsernameInsteadOfPhoneNumber.isChecked()) { if (mUseUsernameInsteadOfPhoneNumber.isChecked()) {
AccountCreator.UsernameStatus usernameStatus = AccountCreator.UsernameStatus usernameStatus =
mAccountCreator.setUsername(mUsername.getText().toString()); getAccountCreator().setUsername(mUsername.getText().toString());
if (usernameStatus != AccountCreator.UsernameStatus.Ok) { if (usernameStatus != AccountCreator.UsernameStatus.Ok) {
mCreate.setEnabled(false); mCreate.setEnabled(false);
mError.setText(getErrorFromUsernameStatus(usernameStatus)); mError.setText(getErrorFromUsernameStatus(usernameStatus));
@ -264,7 +279,7 @@ public class PhoneAccountCreationAssistantActivity extends AssistantActivity {
if (mUseUsernameInsteadOfPhoneNumber.isChecked()) { if (mUseUsernameInsteadOfPhoneNumber.isChecked()) {
username = mUsername.getText().toString(); username = mUsername.getText().toString();
} else { } else {
username = mAccountCreator.getPhoneNumber(); username = getAccountCreator().getPhoneNumber();
} }
if (username != null) { if (username != null) {

View file

@ -56,42 +56,50 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
int proxyConfigIndex = getIntent().getExtras().getInt("AccountNumber"); int proxyConfigIndex = getIntent().getExtras().getInt("AccountNumber");
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
if (core == null) { if (core == null) {
Log.e("[Account Linking] Core not available"); Log.e("[Account Linking Assistant] Core not available");
unexpectedError(); unexpectedError();
return;
} }
ProxyConfig[] proxyConfigs = core.getProxyConfigList(); ProxyConfig[] proxyConfigs = core.getProxyConfigList();
if (proxyConfigIndex >= 0 && proxyConfigIndex < proxyConfigs.length) { if (proxyConfigIndex >= 0 && proxyConfigIndex < proxyConfigs.length) {
ProxyConfig mProxyConfig = proxyConfigs[proxyConfigIndex]; ProxyConfig mProxyConfig = proxyConfigs[proxyConfigIndex];
AccountCreator accountCreator = getAccountCreator();
Address identity = mProxyConfig.getIdentityAddress(); Address identity = mProxyConfig.getIdentityAddress();
if (identity == null) { if (identity == null) {
Log.e("[Account Linking] Proxy doesn't have an identity address"); Log.e("[Account Linking Assistant] Proxy doesn't have an identity address");
unexpectedError(); unexpectedError();
return;
} }
if (!mProxyConfig.getDomain().equals(getString(R.string.default_domain))) { if (!mProxyConfig.getDomain().equals(getString(R.string.default_domain))) {
Log.e( Log.e(
"[Account Linking] Can't link account on domain " "[Account Linking Assistant] Can't link account on domain "
+ mProxyConfig.getDomain()); + mProxyConfig.getDomain());
unexpectedError(); unexpectedError();
return;
} }
mAccountCreator.setUsername(identity.getUsername()); accountCreator.setUsername(identity.getUsername());
AuthInfo authInfo = mProxyConfig.findAuthInfo(); AuthInfo authInfo = mProxyConfig.findAuthInfo();
if (authInfo == null) { if (authInfo == null) {
Log.e("[Account Linking] Auth info not found"); Log.e("[Account Linking Assistant] Auth info not found");
unexpectedError(); unexpectedError();
return;
} }
mAccountCreator.setHa1(authInfo.getHa1()); accountCreator.setHa1(authInfo.getHa1());
accountCreator.setAlgorithm((authInfo.getAlgorithm()));
mAccountCreator.setDomain(getString(R.string.default_domain));
} else { } else {
Log.e("[Account Linking] Proxy config index out of bounds: " + proxyConfigIndex); Log.e(
"[Account Linking Assistant] Proxy config index out of bounds: "
+ proxyConfigIndex);
unexpectedError(); unexpectedError();
return;
} }
} else { } else {
Log.e("[Account Linking] Proxy config index not found"); Log.e("[Account Linking Assistant] Proxy config index not found");
unexpectedError(); unexpectedError();
return;
} }
mCountryPicker = findViewById(R.id.select_country); mCountryPicker = findViewById(R.id.select_country);
@ -112,9 +120,11 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
public void onClick(View v) { public void onClick(View v) {
enableButtonsAndFields(false); enableButtonsAndFields(false);
AccountCreator.Status status = mAccountCreator.isAliasUsed(); AccountCreator.Status status = getAccountCreator().isAliasUsed();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Phone Account Linking] isAliasUsed returned " + status); Log.e(
"[Phone Account Linking Assistant] isAliasUsed returned "
+ status);
enableButtonsAndFields(true); enableButtonsAndFields(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -178,11 +188,15 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
@Override @Override
public void onIsAliasUsed( public void onIsAliasUsed(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Phone Account Linking] onIsAliasUsed status is " + status); Log.i(
"[Phone Account Linking Assistant] onIsAliasUsed status is "
+ status);
if (status.equals(AccountCreator.Status.AliasNotExist)) { if (status.equals(AccountCreator.Status.AliasNotExist)) {
status = mAccountCreator.linkAccount(); status = getAccountCreator().linkAccount();
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e("[Phone Account Linking] linkAccount returned " + status); Log.e(
"[Phone Account Linking Assistant] linkAccount returned "
+ status);
enableButtonsAndFields(true); enableButtonsAndFields(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
} }
@ -200,7 +214,9 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
@Override @Override
public void onLinkAccount( public void onLinkAccount(
AccountCreator creator, AccountCreator.Status status, String resp) { AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i("[Phone Account Linking] onLinkAccount status is " + status); Log.i(
"[Phone Account Linking Assistant] onLinkAccount status is "
+ status);
if (status.equals(AccountCreator.Status.RequestOk)) { if (status.equals(AccountCreator.Status.RequestOk)) {
Intent intent = Intent intent =
new Intent( new Intent(
@ -220,7 +236,12 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); Core core = LinphoneManager.getCore();
if (core != null) {
reloadLinphoneAccountCreatorConfig();
}
getAccountCreator().addListener(mListener);
DialPlan dp = getDialPlanForCurrentCountry(); DialPlan dp = getDialPlanForCurrentCountry();
displayDialPlan(dp); displayDialPlan(dp);
@ -234,7 +255,7 @@ public class PhoneAccountLinkingAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
} }
@Override @Override

View file

@ -40,7 +40,7 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
private ClipboardManager mClipboard; private ClipboardManager mClipboard;
private int mActivationCodeLength; private int mActivationCodeLength;
private boolean mIsLinking; private boolean mIsLinking = false, mIsLogin = false;
private AccountCreatorListenerStub mListener; private AccountCreatorListenerStub mListener;
@Override @Override
@ -50,20 +50,21 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
setContentView(R.layout.assistant_phone_account_validation); setContentView(R.layout.assistant_phone_account_validation);
if (getIntent() != null && getIntent().getBooleanExtra("isLoginVerification", false)) { if (getIntent() != null && getIntent().getBooleanExtra("isLoginVerification", false)) {
findViewById(R.id.title_account_creation).setVisibility(View.VISIBLE); findViewById(R.id.title_account_login).setVisibility(View.VISIBLE);
mIsLogin = true;
} else if (getIntent() != null } else if (getIntent() != null
&& getIntent().getBooleanExtra("isLinkingVerification", false)) { && getIntent().getBooleanExtra("isLinkingVerification", false)) {
mIsLinking = true; mIsLinking = true;
findViewById(R.id.title_account_linking).setVisibility(View.VISIBLE); findViewById(R.id.title_account_linking).setVisibility(View.VISIBLE);
} else { } else {
findViewById(R.id.title_account_activation).setVisibility(View.VISIBLE); findViewById(R.id.title_account_creation).setVisibility(View.VISIBLE);
} }
mActivationCodeLength = mActivationCodeLength =
getResources().getInteger(R.integer.phone_number_validation_code_length); getResources().getInteger(R.integer.phone_number_validation_code_length);
TextView phoneNumber = findViewById(R.id.phone_number); TextView phoneNumber = findViewById(R.id.phone_number);
phoneNumber.setText(mAccountCreator.getPhoneNumber()); phoneNumber.setText(getAccountCreator().getPhoneNumber());
mSmsCode = findViewById(R.id.sms_code); mSmsCode = findViewById(R.id.sms_code);
mSmsCode.addTextChangedListener( mSmsCode.addTextChangedListener(
@ -87,21 +88,27 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
AccountCreator accountCreator = getAccountCreator();
mFinishCreation.setEnabled(false); mFinishCreation.setEnabled(false);
mAccountCreator.setActivationCode(mSmsCode.getText().toString()); accountCreator.setActivationCode(mSmsCode.getText().toString());
AccountCreator.Status status; AccountCreator.Status status;
if (mIsLinking) { if (mIsLinking) {
status = mAccountCreator.activateAlias(); status = accountCreator.activateAlias();
} else if (mIsLogin) {
status = accountCreator.loginLinphoneAccount();
} else { } else {
status = mAccountCreator.activateAccount(); status = accountCreator.activateAccount();
} }
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
Log.e( Log.e(
"[Phone Account Validation] " "[Phone Account Validation] "
+ (mIsLinking + (mIsLinking
? "linkAccount" ? "linkAccount"
: "activateAccount" + " returned ") : (mIsLogin
? "loginLinphoneAccount"
: "activateAccount")
+ " returned ")
+ status); + status);
mFinishCreation.setEnabled(true); mFinishCreation.setEnabled(true);
showGenericErrorDialog(status); showGenericErrorDialog(status);
@ -118,12 +125,7 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
if (status.equals(AccountCreator.Status.AccountActivated)) { if (status.equals(AccountCreator.Status.AccountActivated)) {
createProxyConfigAndLeaveAssistant(); createProxyConfigAndLeaveAssistant();
} else { } else {
mFinishCreation.setEnabled(true); onError(status);
showGenericErrorDialog(status);
if (status.equals(AccountCreator.Status.WrongActivationCode)) {
// TODO do something so the server re-send a SMS
}
} }
} }
@ -135,13 +137,21 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
LinphonePreferences.instance().setLinkPopupTime(""); LinphonePreferences.instance().setLinkPopupTime("");
goToLinphoneActivity(); goToLinphoneActivity();
} else { } else {
mFinishCreation.setEnabled(true); onError(status);
showGenericErrorDialog(status);
if (status.equals(AccountCreator.Status.WrongActivationCode)) {
// TODO do something so the server re-send a SMS
} }
} }
@Override
public void onLoginLinphoneAccount(
AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i(
"[Phone Account Validation] onLoginLinphoneAccount status is "
+ status);
if (status.equals(AccountCreator.Status.RequestOk)) {
createProxyConfigAndLeaveAssistant();
} else {
onError(status);
}
} }
}; };
@ -164,7 +174,7 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAccountCreator.addListener(mListener); getAccountCreator().addListener(mListener);
// Prevent user to go back, it won't be able to come back here after... // Prevent user to go back, it won't be able to come back here after...
mBack.setEnabled(false); mBack.setEnabled(false);
@ -173,6 +183,15 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mAccountCreator.removeListener(mListener); getAccountCreator().removeListener(mListener);
}
private void onError(AccountCreator.Status status) {
mFinishCreation.setEnabled(true);
showGenericErrorDialog(status);
if (status.equals(AccountCreator.Status.WrongActivationCode)) {
// TODO do something so the server re-send a SMS
}
} }
} }

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.utils; package org.linphone.call;
import static android.media.AudioManager.MODE_RINGTONE; import static android.media.AudioManager.MODE_RINGTONE;
import static android.media.AudioManager.STREAM_RING; import static android.media.AudioManager.STREAM_RING;
@ -54,8 +54,6 @@ import org.linphone.receivers.HeadsetReceiver;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
public class AndroidAudioManager { public class AndroidAudioManager {
private static final int LINPHONE_VOLUME_STREAM = STREAM_VOICE_CALL;
private Context mContext; private Context mContext;
private AudioManager mAudioManager; private AudioManager mAudioManager;
private Call mRingingCall; private Call mRingingCall;
@ -415,10 +413,22 @@ public class AndroidAudioManager {
} }
private void adjustVolume(int i) { private void adjustVolume(int i) {
// starting from ICS, volume must be adjusted by the application, at least for if (mAudioManager.isVolumeFixed()) {
// STREAM_VOICE_CALL volume stream Log.e("[Audio Manager] Can't adjust volume, device has it fixed...");
// Keep going just in case...
}
int stream = STREAM_VOICE_CALL;
if (mIsBluetoothHeadsetScoConnected) {
Log.i(
"[Audio Manager] Bluetooth is connected, try to change the volume on STREAM_BLUETOOTH_SCO");
stream = 6; // STREAM_BLUETOOTH_SCO, it's hidden...
}
// starting from ICS, volume must be adjusted by the application,
// at least for STREAM_VOICE_CALL volume stream
mAudioManager.adjustStreamVolume( mAudioManager.adjustStreamVolume(
LINPHONE_VOLUME_STREAM, stream,
i < 0 ? AudioManager.ADJUST_LOWER : AudioManager.ADJUST_RAISE, i < 0 ? AudioManager.ADJUST_LOWER : AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI); AudioManager.FLAG_SHOW_UI);
} }
@ -428,6 +438,7 @@ public class AndroidAudioManager {
public synchronized void bluetoothHeadetConnectionChanged(boolean connected) { public synchronized void bluetoothHeadetConnectionChanged(boolean connected) {
mIsBluetoothHeadsetConnected = connected; mIsBluetoothHeadsetConnected = connected;
mAudioManager.setBluetoothScoOn(connected); mAudioManager.setBluetoothScoOn(connected);
LinphoneManager.getCallManager().refreshInCallActions();
} }
public synchronized void bluetoothHeadetAudioConnectionChanged(boolean connected) { public synchronized void bluetoothHeadetAudioConnectionChanged(boolean connected) {
@ -475,8 +486,10 @@ public class AndroidAudioManager {
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
boolean resultAcknoledged; Log.i("[Audio Manager] [Bluetooth] SCO start/stop thread started");
boolean resultAcknowledged;
int retries = 0; int retries = 0;
do { do {
try { try {
Thread.sleep(200); Thread.sleep(200);
@ -496,26 +509,17 @@ public class AndroidAudioManager {
+ retries); + retries);
mAudioManager.stopBluetoothSco(); mAudioManager.stopBluetoothSco();
} }
resultAcknoledged = isUsingBluetoothAudioRoute() == enable; resultAcknowledged = isUsingBluetoothAudioRoute() == enable;
retries++; retries++;
} }
} while (!resultAcknoledged && retries < 10); } while (!resultAcknowledged && retries < 10);
} }
}.start(); }.start();
} }
private void startBluetooth() { public void bluetoothAdapterStateChanged() {
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()) { if (mBluetoothAdapter.isEnabled()) {
Log.i("[Audio Manager] [Bluetooth] Adapter enabled"); Log.i("[Audio Manager] [Bluetooth] Adapter enabled");
mBluetoothReceiver = new BluetoothReceiver();
mIsBluetoothHeadsetConnected = false; mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false; mIsBluetoothHeadsetScoConnected = false;
@ -534,19 +538,18 @@ public class AndroidAudioManager {
bluetoothHeadetConnectionChanged(true); bluetoothHeadetConnectionChanged(true);
} }
Log.i( Log.i("[Audio Manager] [Bluetooth] Registering bluetooth receiver");
"[Audio Manager] [Bluetooth] Registering bluetooth receiver");
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
filter.addAction( filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction( filter.addAction(
BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
Intent sticky = Intent sticky =
mContext.registerReceiver(mBluetoothReceiver, filter); mContext.registerReceiver(mBluetoothReceiver, filter);
Log.i("[Audio Manager] [Bluetooth] Bluetooth receiver registered");
int state = int state =
sticky.getIntExtra( sticky.getIntExtra(
AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.EXTRA_SCO_AUDIO_STATE,
@ -575,18 +578,37 @@ public class AndroidAudioManager {
public void onServiceDisconnected(int profile) { public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) { if (profile == BluetoothProfile.HEADSET) {
Log.i( Log.i("[Audio Manager] [Bluetooth] HEADSET profile disconnected");
"[Audio Manager] [Bluetooth] HEADSET profile disconnected");
mBluetoothHeadset = null; mBluetoothHeadset = null;
mIsBluetoothHeadsetConnected = false; mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false; mIsBluetoothHeadsetScoConnected = false;
} }
} }
}; };
mBluetoothAdapter.getProfileProxy( mBluetoothAdapter.getProfileProxy(
mContext, bluetoothServiceListener, BluetoothProfile.HEADSET); mContext, bluetoothServiceListener, BluetoothProfile.HEADSET);
} else {
Log.w("[Audio Manager] [Bluetooth] Adapter disabled");
} }
} }
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 !");
}
mBluetoothReceiver = new BluetoothReceiver();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothReceiver, filter);
bluetoothAdapterStateChanged();
}
} }
// HEADSET // HEADSET

View file

@ -48,16 +48,16 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.DialerActivity;
import org.linphone.activities.LinphoneGenericActivity; import org.linphone.activities.LinphoneGenericActivity;
import org.linphone.chat.ChatActivity; import org.linphone.chat.ChatActivity;
import org.linphone.compatibility.Compatibility; import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
@ -66,10 +66,10 @@ import org.linphone.core.Core;
import org.linphone.core.CoreListener; import org.linphone.core.CoreListener;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.AndroidAudioManager;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class CallActivity extends LinphoneGenericActivity public class CallActivity extends LinphoneGenericActivity
implements CallStatusBarFragment.StatsClikedListener, implements CallStatusBarFragment.StatsClikedListener,
@ -82,6 +82,7 @@ public class CallActivity extends LinphoneGenericActivity
private static final int MIC_TO_DISABLE_MUTE = 1; private static final int MIC_TO_DISABLE_MUTE = 1;
private static final int WRITE_EXTERNAL_STORAGE_FOR_RECORDING = 2; private static final int WRITE_EXTERNAL_STORAGE_FOR_RECORDING = 2;
private static final int CAMERA_TO_ACCEPT_UPDATE = 3; private static final int CAMERA_TO_ACCEPT_UPDATE = 3;
private static final int ALL_PERMISSIONS = 4;
private static class HideControlsRunnable implements Runnable { private static class HideControlsRunnable implements Runnable {
private WeakReference<CallActivity> mWeakCallActivity; private WeakReference<CallActivity> mWeakCallActivity;
@ -306,13 +307,13 @@ public class CallActivity extends LinphoneGenericActivity
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
findViewById(R.id.numpad) View numpad = findViewById(R.id.numpad);
.setVisibility( boolean isNumpadVisible = numpad.getVisibility() == View.VISIBLE;
findViewById(R.id.numpad).getVisibility() == View.VISIBLE numpad.setVisibility(isNumpadVisible ? View.GONE : View.VISIBLE);
? View.GONE v.setSelected(!isNumpadVisible);
: View.VISIBLE);
} }
}); });
numpadButton.setSelected(false);
ImageView hangUp = findViewById(R.id.hang_up); ImageView hangUp = findViewById(R.id.hang_up);
hangUp.setOnClickListener( hangUp.setOnClickListener(
@ -387,6 +388,8 @@ public class CallActivity extends LinphoneGenericActivity
if (state == Call.State.End || state == Call.State.Released) { if (state == Call.State.End || state == Call.State.Released) {
if (core.getCallsNb() == 0) { if (core.getCallsNb() == 0) {
finish(); finish();
} else {
showVideoControls(false);
} }
} else if (state == Call.State.PausedByRemote) { } else if (state == Call.State.PausedByRemote) {
if (core.getCurrentCall() != null) { if (core.getCurrentCall() != null) {
@ -437,6 +440,7 @@ public class CallActivity extends LinphoneGenericActivity
Call call = mCore.getCurrentCall(); Call call = mCore.getCurrentCall();
boolean videoEnabled = boolean videoEnabled =
LinphonePreferences.instance().isVideoEnabled() LinphonePreferences.instance().isVideoEnabled()
&& call != null
&& call.getCurrentParams().videoEnabled(); && call.getCurrentParams().videoEnabled();
if (videoEnabled) { if (videoEnabled) {
@ -451,6 +455,11 @@ public class CallActivity extends LinphoneGenericActivity
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
// This also must be done here in case of an outgoing call accepted
// before user granted or denied permissions
// or if an incoming call was answer from the notification
checkAndRequestCallPermissions();
mCore = LinphoneManager.getCore(); mCore = LinphoneManager.getCore();
if (mCore != null) { if (mCore != null) {
mCore.setNativeVideoWindowId(mRemoteVideo); mCore.setNativeVideoWindowId(mRemoteVideo);
@ -557,11 +566,6 @@ public class CallActivity extends LinphoneGenericActivity
super.onDestroy(); super.onDestroy();
} }
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (mAudioManager.onKeyVolumeAdjust(keyCode)) return true; if (mAudioManager.onKeyVolumeAdjust(keyCode)) return true;
@ -581,8 +585,21 @@ public class CallActivity extends LinphoneGenericActivity
public void onRequestPermissionsResult( public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// Permission not granted, won't change anything // Permission not granted, won't change anything
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return;
if (requestCode == ALL_PERMISSIONS) {
for (int index = 0; index < permissions.length; index++) {
int granted = grantResults[index];
if (granted == PackageManager.PERMISSION_GRANTED) {
String permission = permissions[index];
if (Manifest.permission.RECORD_AUDIO.equals(permission)) {
toggleMic();
} else if (Manifest.permission.CAMERA.equals(permission)) {
LinphoneUtils.reloadVideoDevices();
}
}
}
} else {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return;
switch (requestCode) { switch (requestCode) {
case CAMERA_TO_TOGGLE_VIDEO: case CAMERA_TO_TOGGLE_VIDEO:
LinphoneUtils.reloadVideoDevices(); LinphoneUtils.reloadVideoDevices();
@ -600,6 +617,7 @@ public class CallActivity extends LinphoneGenericActivity
break; break;
} }
} }
}
private boolean checkPermission(String permission) { private boolean checkPermission(String permission) {
int granted = getPackageManager().checkPermission(permission, getPackageName()); int granted = getPackageManager().checkPermission(permission, getPackageName());
@ -620,6 +638,57 @@ public class CallActivity extends LinphoneGenericActivity
return true; return true;
} }
private void checkAndRequestCallPermissions() {
ArrayList<String> permissionsList = new ArrayList<>();
int recordAudio =
getPackageManager()
.checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName());
Log.i(
"[Permission] Record audio permission is "
+ (recordAudio == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
int camera =
getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName());
Log.i(
"[Permission] Camera permission is "
+ (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
int readPhoneState =
getPackageManager()
.checkPermission(Manifest.permission.READ_PHONE_STATE, getPackageName());
Log.i(
"[Permission] Read phone state permission is "
+ (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
if (recordAudio != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for record audio");
permissionsList.add(Manifest.permission.RECORD_AUDIO);
}
if (readPhoneState != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for read phone state");
permissionsList.add(Manifest.permission.READ_PHONE_STATE);
}
Call call = mCore.getCurrentCall();
if (LinphonePreferences.instance().shouldInitiateVideoCall()
|| (LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()
&& call != null
&& call.getRemoteParams().videoEnabled())) {
if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for camera");
permissionsList.add(Manifest.permission.CAMERA);
}
}
if (permissionsList.size() > 0) {
String[] permissions = new String[permissionsList.size()];
permissions = permissionsList.toArray(permissions);
ActivityCompat.requestPermissions(this, permissions, ALL_PERMISSIONS);
}
}
@Override @Override
public void onContactsUpdated() { public void onContactsUpdated() {
setCurrentCallContactInformation(); setCurrentCallContactInformation();
@ -825,6 +894,7 @@ public class CallActivity extends LinphoneGenericActivity
boolean videoEnabled = boolean videoEnabled =
LinphonePreferences.instance().isVideoEnabled() LinphonePreferences.instance().isVideoEnabled()
&& call != null
&& call.getCurrentParams().videoEnabled(); && call.getCurrentParams().videoEnabled();
showVideoControls(videoEnabled); showVideoControls(videoEnabled);
} }
@ -1024,7 +1094,7 @@ public class CallActivity extends LinphoneGenericActivity
mConferenceList.removeAllViews(); mConferenceList.removeAllViews();
for (Call call : mCore.getCalls()) { for (Call call : mCore.getCalls()) {
if (call.getConference() != null) { if (call != null && call.getConference() != null) {
if (mCore.isInConference()) { if (mCore.isInConference()) {
displayConferenceCall(call); displayConferenceCall(call);
conferenceDisplayed = true; conferenceDisplayed = true;
@ -1032,11 +1102,16 @@ public class CallActivity extends LinphoneGenericActivity
displayPausedConference(); displayPausedConference();
pausedConferenceDisplayed = true; pausedConferenceDisplayed = true;
} }
} else if (call != currentCall) { } else if (call != null && call != currentCall) {
Call.State state = call.getState();
if (state == Call.State.Paused
|| state == Call.State.PausedByRemote
|| state == Call.State.Pausing) {
displayPausedCall(call); displayPausedCall(call);
callThatIsNotCurrentFound = true; callThatIsNotCurrentFound = true;
} }
} }
}
mCallsList.setVisibility( mCallsList.setVisibility(
pausedConferenceDisplayed || callThatIsNotCurrentFound ? View.VISIBLE : View.GONE); pausedConferenceDisplayed || callThatIsNotCurrentFound ? View.VISIBLE : View.GONE);

View file

@ -27,6 +27,7 @@ import android.os.Bundle;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -36,9 +37,13 @@ import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.LinphoneGenericActivity; import org.linphone.activities.LinphoneGenericActivity;
import org.linphone.call.views.CallIncomingAnswerButton;
import org.linphone.call.views.CallIncomingButtonListener;
import org.linphone.call.views.CallIncomingDeclineButton;
import org.linphone.compatibility.Compatibility; import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Call.State; import org.linphone.core.Call.State;
@ -47,10 +52,6 @@ import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.CallIncomingAnswerButton;
import org.linphone.views.CallIncomingButtonListener;
import org.linphone.views.CallIncomingDeclineButton;
import org.linphone.views.ContactAvatar;
public class CallIncomingActivity extends LinphoneGenericActivity { public class CallIncomingActivity extends LinphoneGenericActivity {
private TextView mName, mNumber; private TextView mName, mNumber;
@ -63,6 +64,7 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Compatibility.setShowWhenLocked(this, true); Compatibility.setShowWhenLocked(this, true);
Compatibility.setTurnScreenOn(this, true); Compatibility.setTurnScreenOn(this, true);
@ -122,13 +124,13 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
Core core, Call call, State state, String message) { Core core, Call call, State state, String message) {
if (call == mCall) { if (call == mCall) {
if (state == State.Connected) { if (state == State.Connected) {
// This is done by the Service listener now // This is done by the LinphoneContext listener now
// startActivity(new Intent(CallOutgoingActivity.this, // startActivity(new Intent(CallOutgoingActivity.this,
// CallActivity.class)); // CallActivity.class));
} }
} }
if (LinphoneManager.getCore().getCallsNb() == 0) { if (state == State.End || state == State.Released) {
finish(); finish();
} }
} }
@ -230,6 +232,7 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
mAlreadyAcceptedOrDeniedCall = true; mAlreadyAcceptedOrDeniedCall = true;
mCall.terminate(); mCall.terminate();
finish();
} }
private void answer() { private void answer() {
@ -276,8 +279,9 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
Log.i("[Permission] Asking for read phone state"); Log.i("[Permission] Asking for read phone state");
permissionsList.add(Manifest.permission.READ_PHONE_STATE); permissionsList.add(Manifest.permission.READ_PHONE_STATE);
} }
if (LinphonePreferences.instance().shouldInitiateVideoCall() if (LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()
|| LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()) { && mCall != null
&& mCall.getRemoteParams().videoEnabled()) {
if (camera != PackageManager.PERMISSION_GRANTED) { if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for camera"); Log.i("[Permission] Asking for camera");
permissionsList.add(Manifest.permission.CAMERA); permissionsList.add(Manifest.permission.CAMERA);

View file

@ -35,11 +35,11 @@ import org.linphone.core.Core;
import org.linphone.core.MediaEncryption; import org.linphone.core.MediaEncryption;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.views.AddressType;
import org.linphone.mediastream.Version; import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.FileUtils; import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.AddressType;
/** Handle call updating, reinvites. */ /** Handle call updating, reinvites. */
public class CallManager { public class CallManager {
@ -87,32 +87,26 @@ public class CallManager {
public void switchCamera() { public void switchCamera() {
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
try { if (core == null) return;
String currentDevice = core.getVideoDevice(); String currentDevice = core.getVideoDevice();
Log.i("[Call Manager] Current camera device is " + currentDevice);
String[] devices = core.getVideoDevicesList(); String[] devices = core.getVideoDevicesList();
int index = 0;
for (String d : devices) { for (String d : devices) {
if (d.equals(currentDevice)) { if (!d.equals(currentDevice) && !d.equals("StaticImage: Static picture")) {
Log.i("[Call Manager] New camera device will be " + d);
core.setVideoDevice(d);
break; 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(); Call call = core.getCurrentCall();
if (call == null) { if (call == null) {
Log.w("[Call Manager] Trying to switch camera while not in call"); Log.i("[Call Manager] Switching camera while not in call");
return; return;
} }
call.update(null); call.update(null);
} catch (ArithmeticException ae) {
Log.e("[Call Manager] [Video] Cannot switch camera: no camera");
}
} }
public boolean acceptCall(Call call) { public boolean acceptCall(Call call) {

View file

@ -37,6 +37,7 @@ import org.linphone.R;
import org.linphone.activities.LinphoneGenericActivity; import org.linphone.activities.LinphoneGenericActivity;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Call.State; import org.linphone.core.Call.State;
@ -46,7 +47,6 @@ import org.linphone.core.Reason;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class CallOutgoingActivity extends LinphoneGenericActivity implements OnClickListener { public class CallOutgoingActivity extends LinphoneGenericActivity implements OnClickListener {
private TextView mName, mNumber; private TextView mName, mNumber;
@ -125,12 +125,12 @@ public class CallOutgoingActivity extends LinphoneGenericActivity implements OnC
.show(); .show();
} }
} else if (state == State.Connected) { } else if (state == State.Connected) {
// This is done by the Service listener now // This is done by the LinphoneContext listener now
// startActivity(new Intent(CallOutgoingActivity.this, // startActivity(new Intent(CallOutgoingActivity.this,
// CallActivity.class)); // CallActivity.class));
} }
if (LinphoneManager.getCore().getCallsNb() == 0) { if (state == State.End || state == State.Released) {
finish(); finish();
} }
} }
@ -283,8 +283,7 @@ public class CallOutgoingActivity extends LinphoneGenericActivity implements OnC
Log.i("[Permission] Asking for read phone state"); Log.i("[Permission] Asking for read phone state");
permissionsList.add(Manifest.permission.READ_PHONE_STATE); permissionsList.add(Manifest.permission.READ_PHONE_STATE);
} }
if (LinphonePreferences.instance().shouldInitiateVideoCall() if (LinphonePreferences.instance().shouldInitiateVideoCall()) {
|| LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()) {
if (camera != PackageManager.PERMISSION_GRANTED) { if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for camera"); Log.i("[Permission] Asking for camera");
permissionsList.add(Manifest.permission.CAMERA); permissionsList.add(Manifest.permission.CAMERA);

View file

@ -29,9 +29,9 @@ import java.util.List;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class CallStatsAdapter extends BaseExpandableListAdapter { public class CallStatsAdapter extends BaseExpandableListAdapter {
private final Context mContext; private final Context mContext;

View file

@ -55,8 +55,8 @@ public class CallStatsFragment extends Fragment {
new CoreListenerStub() { new CoreListenerStub() {
@Override @Override
public void onCallStateChanged( public void onCallStateChanged(
Core lc, Call call, Call.State cstate, String message) { Core core, Call call, Call.State state, String message) {
if (cstate == Call.State.End || cstate == Call.State.Error) { if (state == Call.State.End || state == Call.State.Error) {
mAdapter.updateListItems( mAdapter.updateListItems(
Arrays.asList(LinphoneManager.getCore().getCalls())); Arrays.asList(LinphoneManager.getCore().getCalls()));
} }

View file

@ -288,9 +288,19 @@ public class CallStatusBarFragment extends Fragment {
public void refreshStatusItems(final Call call) { public void refreshStatusItems(final Call call) {
if (call != null) { if (call != null) {
MediaEncryption mediaEncryption = call.getCurrentParams().getMediaEncryption(); if (call.getDir() == Call.Dir.Incoming
&& call.getState() == Call.State.IncomingReceived
&& LinphonePreferences.instance().isMediaEncryptionMandatory()) {
// If the incoming call view is displayed while encryption is mandatory,
// we can safely show the security_ok icon
mEncryption.setImageResource(R.drawable.security_ok);
mEncryption.setVisibility(View.VISIBLE); mEncryption.setVisibility(View.VISIBLE);
return;
}
MediaEncryption mediaEncryption = call.getCurrentParams().getMediaEncryption();
mEncryption.setVisibility(View.VISIBLE);
if (mediaEncryption == MediaEncryption.SRTP if (mediaEncryption == MediaEncryption.SRTP
|| (mediaEncryption == MediaEncryption.ZRTP || (mediaEncryption == MediaEncryption.ZRTP
&& call.getAuthenticationTokenVerified()) && call.getAuthenticationTokenVerified())

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
@ -30,6 +30,8 @@ import org.linphone.core.Call;
import org.linphone.core.CallLog; import org.linphone.core.CallLog;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.dialer.views.AddressAware;
import org.linphone.dialer.views.AddressText;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
@SuppressLint("AppCompatCustomView") @SuppressLint("AppCompatCustomView")

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
public interface CallIncomingButtonListener { public interface CallIncomingButtonListener {
void onAction(); void onAction();

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.view.WindowManager; import android.view.WindowManager;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.call.views;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;

View file

@ -20,6 +20,7 @@
package org.linphone.chat; package org.linphone.chat;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -258,7 +259,18 @@ public class ChatActivity extends MainActivity {
ArrayList<ContactAddress> participants, ArrayList<ContactAddress> participants,
String subject, String subject,
boolean encrypted, boolean encrypted,
boolean isGroupChatRoom) { boolean isGroupChatRoom,
boolean cleanBackStack) {
if (cleanBackStack) {
FragmentManager fm = getFragmentManager();
while (fm.getBackStackEntryCount() > 0) {
fm.popBackStackImmediate();
}
if (isTablet()) {
showEmptyChildFragment();
}
}
Bundle extras = new Bundle(); Bundle extras = new Bundle();
if (peerAddress != null) { if (peerAddress != null) {
extras.putSerializable("RemoteSipUri", peerAddress.asStringUriOnly()); extras.putSerializable("RemoteSipUri", peerAddress.asStringUriOnly());

View file

@ -50,6 +50,7 @@ import java.util.List;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
import org.linphone.core.Content; import org.linphone.core.Content;
@ -57,7 +58,6 @@ import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils; import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils; import org.linphone.utils.ImageUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@ -82,6 +82,7 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
private final RelativeLayout singleFileContent; private final RelativeLayout singleFileContent;
public final CheckBox delete; public final CheckBox delete;
public boolean isEditionEnabled;
private Context mContext; private Context mContext;
private ChatMessageViewHolderClickListener mListener; private ChatMessageViewHolderClickListener mListener;
@ -275,8 +276,12 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (isEditionEnabled) {
ChatMessageViewHolder.this.onClick(v);
} else {
openFile(filePath); openFile(filePath);
} }
}
}); });
} else { } else {
downloadOrCancel.setVisibility(View.VISIBLE); downloadOrCancel.setVisibility(View.VISIBLE);
@ -315,6 +320,9 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (isEditionEnabled) {
ChatMessageViewHolder.this.onClick(v);
} else {
Content c = (Content) v.getTag(); Content c = (Content) v.getTag();
if (!message.isFileTransferInProgress()) { if (!message.isFileTransferInProgress()) {
message.downloadContent(c); message.downloadContent(c);
@ -322,6 +330,7 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
message.cancelFileTransfer(); message.cancelFileTransfer();
} }
} }
}
}); });
} else { } else {
Log.w( Log.w(

View file

@ -119,6 +119,7 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
holder.bubbleLayout.setVisibility(View.GONE); holder.bubbleLayout.setVisibility(View.GONE);
holder.sendInProgress.setVisibility(View.GONE); holder.sendInProgress.setVisibility(View.GONE);
holder.isEditionEnabled = isEditionEnabled();
if (isEditionEnabled()) { if (isEditionEnabled()) {
holder.delete.setVisibility(View.VISIBLE); holder.delete.setVisibility(View.VISIBLE);
holder.delete.setChecked(isSelected(position)); holder.delete.setChecked(isSelected(position));

View file

@ -349,7 +349,7 @@ public class ChatMessagesFragment extends Fragment
new CoreListenerStub() { new CoreListenerStub() {
@Override @Override
public void onCallStateChanged( public void onCallStateChanged(
Core lc, Call call, Call.State state, String message) { Core core, Call call, Call.State state, String message) {
displayChatRoomHeader(); displayChatRoomHeader();
} }
}; };

View file

@ -43,12 +43,14 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactAddress; import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.SearchContactViewHolder; import org.linphone.contacts.SearchContactViewHolder;
import org.linphone.contacts.SearchContactsAdapter; import org.linphone.contacts.SearchContactsAdapter;
import org.linphone.contacts.views.ContactSelectView;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend; import org.linphone.core.ChatRoomBackend;
@ -61,8 +63,6 @@ import org.linphone.core.ProxyConfig;
import org.linphone.core.SearchResult; import org.linphone.core.SearchResult;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.views.ContactSelectView;
import org.linphone.views.LinphoneLinearLayoutManager;
public class ChatRoomCreationFragment extends Fragment public class ChatRoomCreationFragment extends Fragment
implements View.OnClickListener, implements View.OnClickListener,
@ -143,8 +143,8 @@ public class ChatRoomCreationFragment extends Fragment
if (mChatRoomAddress == null && mChatRoomSubject == null) { if (mChatRoomAddress == null && mChatRoomSubject == null) {
mContactsSelectedLayout.removeAllViews(); mContactsSelectedLayout.removeAllViews();
} else { } else {
// Pop the back stack twice so we don't have in stack Group -> Creation // Pop the back stack twice so we don't have in stack
// -> Group // Group -> Creation -> Group
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();
} }
@ -448,7 +448,10 @@ public class ChatRoomCreationFragment extends Fragment
mChatRoom.getLocalAddress(), mChatRoom.getPeerAddress()); mChatRoom.getLocalAddress(), mChatRoom.getPeerAddress());
} }
} else { } else {
ChatRoom chatRoom = core.getChatRoom(address); ChatRoom chatRoom = null;
if (lpc != null) chatRoom = core.getChatRoom(address, lpc.getIdentityAddress());
else chatRoom = core.getChatRoom(address);
if (chatRoom != null) { if (chatRoom != null) {
// Pop back stack so back button takes to the chat rooms list // Pop back stack so back button takes to the chat rooms list
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();

View file

@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
@ -35,7 +36,6 @@ import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.Content; import org.linphone.core.Content;
import org.linphone.core.Participant; import org.linphone.core.Participant;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ChatRoomViewHolder extends RecyclerView.ViewHolder public class ChatRoomViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener { implements View.OnClickListener, View.OnLongClickListener {

View file

@ -35,6 +35,7 @@ import java.util.List;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.MainActivity; import org.linphone.activities.MainActivity;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
@ -44,7 +45,6 @@ import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.LinphoneLinearLayoutManager;
public class ChatRoomsFragment extends Fragment public class ChatRoomsFragment extends Fragment
implements ContactsUpdatedListener, implements ContactsUpdatedListener,
@ -108,7 +108,7 @@ public class ChatRoomsFragment extends Fragment
@Override @Override
public void onClick(View v) { public void onClick(View v) {
((ChatActivity) getActivity()) ((ChatActivity) getActivity())
.showChatRoomCreation(null, null, null, false, false); .showChatRoomCreation(null, null, null, false, false, false);
} }
}); });
@ -117,7 +117,7 @@ public class ChatRoomsFragment extends Fragment
@Override @Override
public void onClick(View v) { public void onClick(View v) {
((ChatActivity) getActivity()) ((ChatActivity) getActivity())
.showChatRoomCreation(null, null, null, false, true); .showChatRoomCreation(null, null, null, false, true, false);
} }
}); });
@ -137,8 +137,13 @@ public class ChatRoomsFragment extends Fragment
} }
@Override @Override
public void onMessageReceived(Core core, ChatRoom cr, ChatMessage message) { public void onMessageReceived(Core core, ChatRoom room, ChatMessage message) {
refreshChatRoom(cr); refreshChatRoom(room);
}
@Override
public void onChatRoomSubjectChanged(Core core, ChatRoom room) {
refreshChatRoom(room);
} }
@Override @Override
@ -154,9 +159,10 @@ public class ChatRoomsFragment extends Fragment
@Override @Override
public void onChatRoomStateChanged( public void onChatRoomStateChanged(
Core core, ChatRoom cr, ChatRoom.State state) { Core core, ChatRoom room, ChatRoom.State state) {
if (state == ChatRoom.State.Created) { if (state == ChatRoom.State.Created) {
refreshChatRoom(cr); refreshChatRoom(room);
scrollToTop();
} }
} }
}; };
@ -253,6 +259,9 @@ public class ChatRoomsFragment extends Fragment
mWaitLayout.setVisibility(View.VISIBLE); mWaitLayout.setVisibility(View.VISIBLE);
} }
((ChatActivity) getActivity()).displayMissedChats(); ((ChatActivity) getActivity()).displayMissedChats();
if (getResources().getBoolean(R.bool.isTablet))
((ChatActivity) getActivity()).showEmptyChildFragment();
} }
@Override @Override
@ -263,6 +272,10 @@ public class ChatRoomsFragment extends Fragment
} }
} }
private void scrollToTop() {
mChatRoomsList.getLayoutManager().scrollToPosition(0);
}
private void refreshChatRoom(ChatRoom cr) { private void refreshChatRoom(ChatRoom cr) {
ChatRoomViewHolder holder = (ChatRoomViewHolder) cr.getUserData(); ChatRoomViewHolder holder = (ChatRoomViewHolder) cr.getUserData();
if (holder != null) { if (holder != null) {

View file

@ -29,12 +29,12 @@ import java.util.List;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatRoomSecurityLevel; import org.linphone.core.ChatRoomSecurityLevel;
import org.linphone.core.Participant; import org.linphone.core.Participant;
import org.linphone.core.ParticipantDevice; import org.linphone.core.ParticipantDevice;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
class DevicesAdapter extends BaseExpandableListAdapter { class DevicesAdapter extends BaseExpandableListAdapter {
private final Context mContext; private final Context mContext;

View file

@ -26,16 +26,12 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ExpandableListView; import android.widget.ExpandableListView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.Participant; import org.linphone.core.Participant;
@ -43,7 +39,6 @@ import org.linphone.core.ParticipantDevice;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
public class DevicesFragment extends Fragment { public class DevicesFragment extends Fragment {
private TextView mTitle;
private ExpandableListView mExpandableList; private ExpandableListView mExpandableList;
private DevicesAdapter mAdapter; private DevicesAdapter mAdapter;
@ -114,9 +109,6 @@ public class DevicesFragment extends Fragment {
initChatRoom(); initChatRoom();
mTitle = view.findViewById(R.id.title);
initHeader();
ImageView backButton = view.findViewById(R.id.back); ImageView backButton = view.findViewById(R.id.back);
backButton.setOnClickListener( backButton.setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@ -146,24 +138,6 @@ public class DevicesFragment extends Fragment {
mRoom = core.getChatRoom(mRoomAddr, mLocalSipAddr); mRoom = core.getChatRoom(mRoomAddr, mLocalSipAddr);
} }
private void initHeader() {
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
Address remoteParticipantAddr = mRoomAddr;
if (mRoom.getParticipants().length > 0) {
remoteParticipantAddr = mRoom.getParticipants()[0].getAddress();
}
LinphoneContact c =
ContactsManager.getInstance().findContactFromAddress(remoteParticipantAddr);
String displayName;
if (c != null) {
displayName = c.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteParticipantAddr);
}
mTitle.setText(getString(R.string.chat_room_devices).replace("%s", displayName));
}
}
private void initValues() { private void initValues() {
if (mAdapter == null) { if (mAdapter == null) {
mAdapter = new DevicesAdapter(getActivity()); mAdapter = new DevicesAdapter(getActivity());

View file

@ -30,9 +30,9 @@ import org.linphone.LinphoneContext;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactAddress; import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.Participant; import org.linphone.core.Participant;
import org.linphone.views.ContactAvatar;
class GroupInfoAdapter extends RecyclerView.Adapter<GroupInfoViewHolder> { class GroupInfoAdapter extends RecyclerView.Adapter<GroupInfoViewHolder> {
private List<ContactAddress> mItems; private List<ContactAddress> mItems;

View file

@ -23,7 +23,6 @@ import static android.content.Context.INPUT_METHOD_SERVICE;
import android.app.Dialog; import android.app.Dialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -42,6 +41,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactAddress; import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
@ -56,7 +56,6 @@ import org.linphone.core.Factory;
import org.linphone.core.Participant; import org.linphone.core.Participant;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.LinphoneLinearLayoutManager;
public class GroupInfoFragment extends Fragment { public class GroupInfoFragment extends Fragment {
private ImageView mConfirmButton; private ImageView mConfirmButton;
@ -146,7 +145,11 @@ public class GroupInfoFragment extends Fragment {
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (mIsAlreadyCreatedGroup) {
((ChatActivity) getActivity()).goBack(); ((ChatActivity) getActivity()).goBack();
} else {
goBackToChatCreationFragment();
}
} }
}); });
@ -262,6 +265,16 @@ public class GroupInfoFragment extends Fragment {
public void onSubjectChanged(ChatRoom cr, EventLog event_log) { public void onSubjectChanged(ChatRoom cr, EventLog event_log) {
mSubjectField.setText(event_log.getSubject()); mSubjectField.setText(event_log.getSubject());
} }
@Override
public void onParticipantAdded(ChatRoom cr, EventLog eventLog) {
refreshParticipantsList();
}
@Override
public void onParticipantRemoved(ChatRoom cr, EventLog eventLog) {
refreshParticipantsList();
}
}; };
if (mChatRoom != null) { if (mChatRoom != null) {
@ -300,26 +313,14 @@ public class GroupInfoFragment extends Fragment {
} }
private void goBackToChatCreationFragment() { private void goBackToChatCreationFragment() {
boolean previousFragmentInBackStackIsChatRoomCreation = false;
FragmentManager fragmentManager = getActivity().getFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
if (count > 1) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(count - 1);
if ("Chat room creation".equals(entry.getName())) {
previousFragmentInBackStackIsChatRoomCreation = true;
((ChatActivity) getActivity()).goBack();
}
}
if (!previousFragmentInBackStackIsChatRoomCreation) {
((ChatActivity) getActivity()) ((ChatActivity) getActivity())
.showChatRoomCreation( .showChatRoomCreation(
mGroupChatRoomAddress, mGroupChatRoomAddress,
mParticipants, mParticipants,
mSubject, mSubject,
mIsEncryptionEnabled, mIsEncryptionEnabled,
true); true,
} !mIsAlreadyCreatedGroup);
} }
private void refreshParticipantsList() { private void refreshParticipantsList() {
@ -485,7 +486,9 @@ public class GroupInfoFragment extends Fragment {
} }
Address[] participantsToAdd = new Address[toAdd.size()]; Address[] participantsToAdd = new Address[toAdd.size()];
toAdd.toArray(participantsToAdd); toAdd.toArray(participantsToAdd);
mChatRoom.addParticipants(participantsToAdd); if (!mChatRoom.addParticipants(participantsToAdd)) {
// TODO error
}
// Pop back stack to go back to the Messages fragment // Pop back stack to go back to the Messages fragment
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();
} }

View file

@ -32,6 +32,7 @@ import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub; import org.linphone.core.ChatMessageListenerStub;
@ -40,7 +41,6 @@ import org.linphone.core.Core;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.ParticipantImdnState; import org.linphone.core.ParticipantImdnState;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ImdnFragment extends Fragment { public class ImdnFragment extends Fragment {
private LayoutInflater mInflater; private LayoutInflater mInflater;

View file

@ -140,7 +140,6 @@ class ApiTwentyFourPlus {
return new Notification.Builder(context) return new Notification.Builder(context)
.setStyle(new Notification.DecoratedCustomViewStyle()) .setStyle(new Notification.DecoratedCustomViewStyle())
.setSmallIcon(R.drawable.topbar_call_notification) .setSmallIcon(R.drawable.topbar_call_notification)
.setLargeIcon(contactIcon)
.setContentTitle(contactName) .setContentTitle(contactName)
.setContentText(context.getString(R.string.incall_notif_incoming)) .setContentText(context.getString(R.string.incall_notif_incoming))
.setContentIntent(intent) .setContentIntent(intent)

View file

@ -183,7 +183,7 @@ class ApiTwentyOnePlus {
} }
public static Notification createMissedCallNotification( public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) { Context context, String title, String text, PendingIntent intent, int count) {
return new Notification.Builder(context) return new Notification.Builder(context)
.setContentTitle(title) .setContentTitle(title)
@ -201,6 +201,7 @@ class ApiTwentyOnePlus {
.setPriority(Notification.PRIORITY_HIGH) .setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis()) .setWhen(System.currentTimeMillis())
.setShowWhen(true) .setShowWhen(true)
.setNumber(count)
.build(); .build();
} }

View file

@ -76,7 +76,7 @@ class ApiTwentySixPlus {
CharSequence name = context.getString(R.string.content_title_notification_service); CharSequence name = context.getString(R.string.content_title_notification_service);
String description = context.getString(R.string.content_title_notification_service); String description = context.getString(R.string.content_title_notification_service);
NotificationChannel channel = NotificationChannel channel =
new NotificationChannel(id, name, NotificationManager.IMPORTANCE_NONE); new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
channel.setDescription(description); channel.setDescription(description);
channel.enableVibration(false); channel.enableVibration(false);
channel.enableLights(false); channel.enableLights(false);
@ -191,7 +191,6 @@ class ApiTwentySixPlus {
context, context.getString(R.string.notification_channel_id)) context, context.getString(R.string.notification_channel_id))
.setStyle(new Notification.DecoratedCustomViewStyle()) .setStyle(new Notification.DecoratedCustomViewStyle())
.setSmallIcon(R.drawable.topbar_call_notification) .setSmallIcon(R.drawable.topbar_call_notification)
.setLargeIcon(contactIcon)
.setContentTitle(contactName) .setContentTitle(contactName)
.setContentText(context.getString(R.string.incall_notif_incoming)) .setContentText(context.getString(R.string.incall_notif_incoming))
.setContentIntent(intent) .setContentIntent(intent)
@ -255,7 +254,7 @@ class ApiTwentySixPlus {
} }
public static Notification createMissedCallNotification( public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) { Context context, String title, String text, PendingIntent intent, int count) {
return new Notification.Builder( return new Notification.Builder(
context, context.getString(R.string.notification_channel_id)) context, context.getString(R.string.notification_channel_id))
.setContentTitle(title) .setContentTitle(title)
@ -269,6 +268,7 @@ class ApiTwentySixPlus {
.setPriority(Notification.PRIORITY_HIGH) .setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis()) .setWhen(System.currentTimeMillis())
.setShowWhen(true) .setShowWhen(true)
.setNumber(count)
.setColor(context.getColor(R.color.notification_led_color)) .setColor(context.getColor(R.color.notification_led_color))
.build(); .build();
} }

View file

@ -23,6 +23,7 @@ import android.annotation.TargetApi;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.os.PowerManager; import android.os.PowerManager;
import android.service.notification.StatusBarNotification;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address; import org.linphone.core.Address;
@ -111,4 +112,8 @@ class ApiTwentyThreePlus {
return true; return true;
} }
public static StatusBarNotification[] getActiveNotifications(NotificationManager manager) {
return manager.getActiveNotifications();
}
} }

View file

@ -22,6 +22,7 @@ package org.linphone.compatibility;
import android.app.Activity; import android.app.Activity;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
@ -30,6 +31,7 @@ import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.mediastream.Version; import org.linphone.mediastream.Version;
import org.linphone.notifications.Notifiable; import org.linphone.notifications.Notifiable;
@ -103,11 +105,12 @@ public class Compatibility {
} }
public static Notification createMissedCallNotification( public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) { Context context, String title, String text, PendingIntent intent, int count) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) { if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createMissedCallNotification(context, title, text, intent); return ApiTwentySixPlus.createMissedCallNotification(
context, title, text, intent, count);
} }
return ApiTwentyOnePlus.createMissedCallNotification(context, title, text, intent); return ApiTwentyOnePlus.createMissedCallNotification(context, title, text, intent, count);
} }
public static Notification createInCallNotification( public static Notification createInCallNotification(
@ -308,4 +311,12 @@ public class Compatibility {
} }
return null; return null;
} }
public static StatusBarNotification[] getActiveNotifications(NotificationManager manager) {
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
return ApiTwentyThreePlus.getActiveNotifications(manager);
}
return new StatusBarNotification[0];
}
} }

View file

@ -23,6 +23,7 @@ import android.content.ContentProviderOperation;
import android.content.ContentProviderResult; import android.content.ContentProviderResult;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract; import android.provider.ContactsContract;
@ -30,6 +31,8 @@ import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import org.linphone.LinphoneContext; import org.linphone.LinphoneContext;
@ -41,10 +44,12 @@ class AndroidContact implements Serializable {
private String mAndroidRawId; private String mAndroidRawId;
private boolean isAndroidRawIdLinphone; private boolean isAndroidRawIdLinphone;
private final transient ArrayList<ContentProviderOperation> mChangesToCommit; private final transient ArrayList<ContentProviderOperation> mChangesToCommit;
private byte[] mTempPicture;
AndroidContact() { AndroidContact() {
mChangesToCommit = new ArrayList<>(); mChangesToCommit = new ArrayList<>();
isAndroidRawIdLinphone = false; isAndroidRawIdLinphone = false;
mTempPicture = null;
} }
String getAndroidId() { String getAndroidId() {
@ -78,6 +83,12 @@ class AndroidContact implements Serializable {
String rawId = String.valueOf(ContentUris.parseId(results[0].uri)); String rawId = String.valueOf(ContentUris.parseId(results[0].uri));
if (mAndroidId == null) { if (mAndroidId == null) {
Log.i("[Contact] Contact created with RAW ID " + rawId); Log.i("[Contact] Contact created with RAW ID " + rawId);
mAndroidRawId = rawId;
if (mTempPicture != null) {
Log.i(
"[Contact] Contact has been created, raw is is available, time to set the photo");
setPhoto(mTempPicture);
}
final String[] projection = final String[] projection =
new String[] {ContactsContract.RawContacts.CONTACT_ID}; new String[] {ContactsContract.RawContacts.CONTACT_ID};
@ -146,6 +157,7 @@ class AndroidContact implements Serializable {
} }
void deleteAndroidContact() { void deleteAndroidContact() {
Log.i("[Contact] Deleting Android contact ", this);
ContactsManager.getInstance().delete(mAndroidId); ContactsManager.getInstance().delete(mAndroidId);
} }
@ -550,33 +562,44 @@ class AndroidContact implements Serializable {
return; return;
} }
if (mAndroidId == null) { if (mAndroidRawId == null) {
Log.i("[Contact] Setting picture to new contact."); Log.w("[Contact] Can't set picture for not already created contact, will do it later");
addChangesToCommit( mTempPicture = photo;
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo)
.build());
} else { } else {
Log.i( Log.i(
"[Contact] Setting picture to existing contact " "[Contact] Setting picture to an already created raw contact [",
+ mAndroidId mAndroidRawId,
+ " (" "]");
+ mAndroidRawId try {
+ ")"); long rawId = Long.parseLong(mAndroidRawId);
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) Uri rawContactPhotoUri =
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId) Uri.withAppendedPath(
.withValue( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawId),
ContactsContract.Data.MIMETYPE, RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo) if (rawContactPhotoUri != null) {
.withValue(ContactsContract.Data.IS_PRIMARY, 1) ContentResolver resolver =
.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) LinphoneContext.instance().getApplicationContext().getContentResolver();
.build()); AssetFileDescriptor fd =
resolver.openAssetFileDescriptor(rawContactPhotoUri, "rw");
OutputStream os = fd.createOutputStream();
os.write(photo);
os.close();
fd.close();
} else {
Log.e(
"[Contact] Failed to get raw contact photo URI for raw contact id [",
rawId,
"], aborting");
}
} catch (NumberFormatException nfe) {
Log.e("[Contact] Couldn't parse raw id [", mAndroidId, "], aborting");
} catch (IOException ioe) {
Log.e("[Contact] Couldn't set picture, IO error: ", ioe);
} catch (Exception e) {
Log.e("[Contact] Couldn't set picture, unknown error: ", e);
}
} }
} }

View file

@ -79,21 +79,6 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
protected AsyncContactsData doInBackground(Void... params) { protected AsyncContactsData doInBackground(Void... params) {
Log.i("[Contacts Manager] Background synchronization started"); Log.i("[Contacts Manager] Background synchronization started");
String selection = null;
if (mContext.getResources().getBoolean(R.bool.fetch_contacts_from_default_directory)) {
Log.i("[Contacts Manager] Only fetching contacts in default directory");
selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1";
}
Cursor c =
mContext.getContentResolver()
.query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
selection,
null,
null);
HashMap<String, LinphoneContact> androidContactsCache = new HashMap<>(); HashMap<String, LinphoneContact> androidContactsCache = new HashMap<>();
AsyncContactsData data = new AsyncContactsData(); AsyncContactsData data = new AsyncContactsData();
List<String> nativeIds = new ArrayList<>(); List<String> nativeIds = new ArrayList<>();
@ -111,10 +96,12 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
LinphoneContact contact = (LinphoneContact) friend.getUserData(); LinphoneContact contact = (LinphoneContact) friend.getUserData();
if (contact != null) { if (contact != null) {
contact.clearAddresses();
if (contact.getAndroidId() != null) { if (contact.getAndroidId() != null) {
contact.clearAddresses();
androidContactsCache.put(contact.getAndroidId(), contact); androidContactsCache.put(contact.getAndroidId(), contact);
nativeIds.add(contact.getAndroidId()); nativeIds.add(contact.getAndroidId());
} else {
data.contacts.add(contact);
} }
} else { } else {
if (friend.getRefKey() != null) { if (friend.getRefKey() != null) {
@ -134,6 +121,21 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
} }
} }
if (ContactsManager.getInstance().hasReadContactsAccess()) {
String selection = null;
if (mContext.getResources().getBoolean(R.bool.fetch_contacts_from_default_directory)) {
Log.i("[Contacts Manager] Only fetching contacts in default directory");
selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1";
}
Cursor c =
mContext.getContentResolver()
.query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
selection,
null,
null);
if (c != null) { if (c != null) {
Log.i("[Contacts Manager] Found " + c.getCount() + " entries in cursor"); Log.i("[Contacts Manager] Found " + c.getCount() + " entries in cursor");
while (c.moveToNext()) { while (c.moveToNext()) {
@ -142,6 +144,7 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
return data; return data;
} }
try {
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID)); String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
boolean starred = boolean starred =
c.getInt(c.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1; c.getInt(c.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1;
@ -161,8 +164,14 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
} }
contact.syncValuesFromAndroidCusor(c); contact.syncValuesFromAndroidCusor(c);
} catch (IllegalStateException ise) {
Log.e(
"[Contacts Manager] Couldn't get values from cursor, exception: ",
ise);
}
} }
c.close(); c.close();
}
FriendList[] friendLists = core.getFriendsLists(); FriendList[] friendLists = core.getFriendsLists();
for (FriendList list : friendLists) { for (FriendList list : friendLists) {
@ -188,7 +197,13 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
} }
Collection<LinphoneContact> contacts = androidContactsCache.values(); Collection<LinphoneContact> contacts = androidContactsCache.values();
Log.i("[Contacts Manager] Found " + contacts.size() + " contacts"); // New friends count will be 0 after the first contacts fetch
Log.i(
"[Contacts Manager] Found "
+ contacts.size()
+ " native contacts plus "
+ data.contacts.size()
+ " friends in the configuration file");
for (LinphoneContact contact : contacts) { for (LinphoneContact contact : contacts) {
if (isCancelled()) { if (isCancelled()) {
Log.w("[Contacts Manager] Task cancelled"); Log.w("[Contacts Manager] Task cancelled");

View file

@ -36,6 +36,7 @@ import android.widget.TableLayout;
import android.widget.TextView; import android.widget.TextView;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend; import org.linphone.core.ChatRoomBackend;
@ -50,7 +51,6 @@ import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactDetailsFragment extends Fragment implements ContactsUpdatedListener { public class ContactDetailsFragment extends Fragment implements ContactsUpdatedListener {
private LinphoneContact mContact; private LinphoneContact mContact;
@ -90,6 +90,11 @@ public class ContactDetailsFragment extends Fragment implements ContactsUpdatedL
} }
}); });
if (mContact != null
&& getResources().getBoolean(R.bool.forbid_pure_linphone_contacts_edition)) {
editContact.setVisibility(mContact.isAndroidContact() ? View.VISIBLE : View.GONE);
}
ImageView deleteContact = mView.findViewById(R.id.deleteContact); ImageView deleteContact = mView.findViewById(R.id.deleteContact);
deleteContact.setOnClickListener( deleteContact.setOnClickListener(
new OnClickListener() { new OnClickListener() {
@ -359,6 +364,8 @@ public class ContactDetailsFragment extends Fragment implements ContactsUpdatedL
private void goToChat(String tag, boolean isSecured) { private void goToChat(String tag, boolean isSecured) {
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
if (core == null) return;
Address participant = Factory.instance().createAddress(tag); Address participant = Factory.instance().createAddress(tag);
ProxyConfig defaultProxyConfig = core.getDefaultProxyConfig(); ProxyConfig defaultProxyConfig = core.getDefaultProxyConfig();
@ -404,6 +411,18 @@ public class ContactDetailsFragment extends Fragment implements ContactsUpdatedL
} }
} }
} }
} else {
if (isSecured) {
Log.e(
"[Contact Details Fragment] Can't create a secured chat room without proxy config");
return;
}
ChatRoom room = core.getChatRoom(participant);
if (room != null) {
((ContactsActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
}
} }
} }
} }

View file

@ -26,13 +26,12 @@ import android.app.Dialog;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
@ -53,12 +52,13 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version; import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.FileUtils; import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactEditorFragment extends Fragment { public class ContactEditorFragment extends Fragment {
private static final int ADD_PHOTO = 1337; private static final int ADD_PHOTO = 1337;
@ -136,12 +136,15 @@ public class ContactEditorFragment extends Fragment {
if (mIsNewContact) { if (mIsNewContact) {
boolean areAllFielsEmpty = true; boolean areAllFielsEmpty = true;
for (LinphoneNumberOrAddress nounoa : mNumbersAndAddresses) { for (LinphoneNumberOrAddress nounoa : mNumbersAndAddresses) {
if (nounoa.getValue() != null && !nounoa.getValue().equals("")) { String value = nounoa.getValue();
if (value != null && !value.trim().isEmpty()) {
areAllFielsEmpty = false; areAllFielsEmpty = false;
break; break;
} }
} }
if (areAllFielsEmpty) { if (areAllFielsEmpty) {
Log.i(
"[Contact Editor] All SIP and phone fields are empty, aborting");
getFragmentManager().popBackStackImmediate(); getFragmentManager().popBackStackImmediate();
return; return;
} }
@ -154,37 +157,36 @@ public class ContactEditorFragment extends Fragment {
true); true);
if (mPhotoToAdd != null) { if (mPhotoToAdd != null) {
Log.i("[Contact Editor] Found picture to set to contact");
mContact.setPhoto(mPhotoToAdd); mContact.setPhoto(mPhotoToAdd);
} }
for (LinphoneNumberOrAddress noa : mNumbersAndAddresses) { for (LinphoneNumberOrAddress noa : mNumbersAndAddresses) {
if (noa.getValue() == null || noa.getValue().isEmpty()) { String value = noa.getValue();
if (noa.getOldValue() != null && !noa.getOldValue().isEmpty()) { String oldValue = noa.getOldValue();
Log.i("[Contact Editor] Removing number " + noa.getOldValue());
if (value == null || value.trim().isEmpty()) {
if (oldValue != null && !oldValue.isEmpty()) {
Log.i("[Contact Editor] Removing number: ", oldValue);
mContact.removeNumberOrAddress(noa); mContact.removeNumberOrAddress(noa);
} }
} else { } else {
if (noa.getOldValue() != null if (oldValue != null && oldValue.equals(value)) {
&& noa.getOldValue().equals(noa.getValue())) { Log.i("[Contact Editor] Keeping existing number: ", value);
Log.i(
"[Contact Editor] Keeping existing number "
+ noa.getValue());
continue; continue;
} }
if (noa.isSIPAddress()) { if (noa.isSIPAddress()) {
noa.setValue(LinphoneUtils.getFullAddressFromUsername(value));
noa.setValue(
LinphoneUtils.getFullAddressFromUsername(
noa.getValue()));
} }
Log.i("[Contact Editor] Adding new number " + noa.getValue()); Log.i("[Contact Editor] Adding new number: ", value);
mContact.addOrUpdateNumberOrAddress(noa); mContact.addOrUpdateNumberOrAddress(noa);
} }
} }
if (!mOrganization.getText().toString().isEmpty() || !mIsNewContact) { if (!mOrganization.getText().toString().isEmpty() || !mIsNewContact) {
Log.i("[Contact Editor] Setting organization field: ", mOrganization);
mContact.setOrganization(mOrganization.getText().toString(), true); mContact.setOrganization(mOrganization.getText().toString(), true);
} }
@ -194,6 +196,8 @@ public class ContactEditorFragment extends Fragment {
// Ensure fetch will be done so the new contact appears in the contacts // Ensure fetch will be done so the new contact appears in the contacts
// list: contacts content observer may not be notified if contacts sync // list: contacts content observer may not be notified if contacts sync
// is disabled at system level // is disabled at system level
Log.i(
"[Contact Editor] New contact created, starting fetch contacts task");
ContactsManager.getInstance().fetchContactsAsync(); ContactsManager.getInstance().fetchContactsAsync();
} }
@ -306,9 +310,18 @@ public class ContactEditorFragment extends Fragment {
new OnClickListener() { new OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
ContactsActivity contactsActivity = ((ContactsActivity) getActivity());
if (contactsActivity != null) {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
if (contactsActivity.checkPermissions(permissions)) {
pickImage(); pickImage();
((ContactsActivity) getActivity()) } else {
.requestPermissionIfNotGranted(Manifest.permission.CAMERA); contactsActivity.requestPermissionsIfNotGranted(permissions);
}
}
} }
}); });
@ -386,15 +399,18 @@ public class ContactEditorFragment extends Fragment {
editContactPicture(null, bm); editContactPicture(null, bm);
} else if (data != null && data.getData() != null) { } else if (data != null && data.getData() != null) {
Uri selectedImageUri = data.getData(); Uri selectedImageUri = data.getData();
String filePath = FileUtils.getRealPathFromURI(getActivity(), selectedImageUri);
if (filePath != null) {
editContactPicture(filePath, null);
} else {
try { try {
Bitmap selectedImage = Bitmap selectedImage =
MediaStore.Images.Media.getBitmap( MediaStore.Images.Media.getBitmap(
getActivity().getContentResolver(), selectedImageUri); getActivity().getContentResolver(), selectedImageUri);
selectedImage =
Bitmap.createScaledBitmap(selectedImage, PHOTO_SIZE, PHOTO_SIZE, false);
editContactPicture(null, selectedImage); editContactPicture(null, selectedImage);
} catch (IOException e) { } catch (IOException e) {
Log.e(e); Log.e("[Contact Editor] IO error: ", e);
}
} }
} else if (mPickedPhotoForContactUri != null) { } else if (mPickedPhotoForContactUri != null) {
String filePath = mPickedPhotoForContactUri.getPath(); String filePath = mPickedPhotoForContactUri.getPath();
@ -426,11 +442,13 @@ public class ContactEditorFragment extends Fragment {
} }
} }
if (mPhotoToAdd == null) {
if (mContact != null) { if (mContact != null) {
ContactAvatar.displayAvatar(mContact, mView.findViewById(R.id.avatar_layout)); ContactAvatar.displayAvatar(mContact, mView.findViewById(R.id.avatar_layout));
} else { } else {
ContactAvatar.displayAvatar("", mView.findViewById(R.id.avatar_layout)); ContactAvatar.displayAvatar("", mView.findViewById(R.id.avatar_layout));
} }
}
mSipAddresses = initSipAddressFields(mContact); mSipAddresses = initSipAddressFields(mContact);
mNumbers = initNumbersFields(mContact); mNumbers = initNumbersFields(mContact);
@ -438,27 +456,24 @@ public class ContactEditorFragment extends Fragment {
private void pickImage() { private void pickImage() {
mPickedPhotoForContactUri = null; mPickedPhotoForContactUri = null;
final List<Intent> cameraIntents = new ArrayList<>(); List<Intent> cameraIntents = new ArrayList<>();
final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Handles image & video picking
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
// Allows to capture directly from the camera
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = File file =
new File( new File(
FileUtils.getStorageDirectory(getActivity()), FileUtils.getStorageDirectory(getActivity()),
getString(R.string.temp_photo_name)); getString(R.string.temp_photo_name_with_date)
.replace("%s", System.currentTimeMillis() + ".jpeg"));
mPickedPhotoForContactUri = Uri.fromFile(file); mPickedPhotoForContactUri = Uri.fromFile(file);
captureIntent.putExtra("outputX", PHOTO_SIZE);
captureIntent.putExtra("outputY", PHOTO_SIZE);
captureIntent.putExtra("aspectX", 0);
captureIntent.putExtra("aspectY", 0);
captureIntent.putExtra("scale", true);
captureIntent.putExtra("return-data", false);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mPickedPhotoForContactUri); captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mPickedPhotoForContactUri);
cameraIntents.add(captureIntent); cameraIntents.add(captureIntent);
final Intent galleryIntent = new Intent(); Intent chooserIntent =
galleryIntent.setType("image/*");
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
final Intent chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.image_picker_title)); Intent.createChooser(galleryIntent, getString(R.string.image_picker_title));
chooserIntent.putExtra( chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[] {})); Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[] {}));
@ -467,44 +482,59 @@ public class ContactEditorFragment extends Fragment {
} }
private void editContactPicture(String filePath, Bitmap image) { private void editContactPicture(String filePath, Bitmap image) {
int orientation = ExifInterface.ORIENTATION_NORMAL;
if (image == null) { if (image == null) {
Log.i(
"[Contact Editor] Bitmap is null, trying to decode image from file [",
filePath,
"]");
image = BitmapFactory.decodeFile(filePath); image = BitmapFactory.decodeFile(filePath);
try {
ExifInterface ei = new ExifInterface(filePath);
orientation =
ei.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
Log.i("[Contact Editor] Exif rotation is ", orientation);
} catch (IOException e) {
Log.e("[Contact Editor] Failed to get Exif rotation, error is ", e);
}
} else {
}
if (image == null) {
Log.e(
"[Contact Editor] Couldn't get bitmap from either filePath [",
filePath,
"] nor image");
return;
} }
Bitmap scaledPhoto; switch (orientation) {
int size = getThumbnailSize(); case ExifInterface.ORIENTATION_ROTATE_90:
if (size > 0) { image = ImageUtils.rotateImage(image, 90);
scaledPhoto = Bitmap.createScaledBitmap(image, size, size, false); break;
} else { case ExifInterface.ORIENTATION_ROTATE_180:
scaledPhoto = Bitmap.createBitmap(image); image = ImageUtils.rotateImage(image, 180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
image = ImageUtils.rotateImage(image, 270);
break;
case ExifInterface.ORIENTATION_NORMAL:
// Nothing to do
break;
default:
Log.w("[Contact Editor] Unexpected orientation ", orientation);
} }
image.recycle();
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
scaledPhoto.compress(Bitmap.CompressFormat.PNG, 0, stream); image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
mContactPicture.setImageBitmap(scaledPhoto);
mPhotoToAdd = stream.toByteArray(); mPhotoToAdd = stream.toByteArray();
}
private int getThumbnailSize() { Bitmap roundPicture = ImageUtils.getRoundBitmap(image);
int value = -1; ContactAvatar.displayAvatar(roundPicture, mView.findViewById(R.id.avatar_layout));
Cursor c = image.recycle();
getActivity()
.getContentResolver()
.query(
DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
new String[] {DisplayPhoto.THUMBNAIL_MAX_DIM},
null,
null,
null);
try {
c.moveToFirst();
value = c.getInt(0);
} catch (Exception e) {
Log.e(e);
}
c.close();
return value;
} }
private LinearLayout initNumbersFields(final LinphoneContact contact) { private LinearLayout initNumbersFields(final LinphoneContact contact) {

View file

@ -102,6 +102,15 @@ public class ContactsActivity extends MainActivity {
mContactsSelected.setVisibility(View.VISIBLE); mContactsSelected.setVisibility(View.VISIBLE);
} }
@Override
protected void onPause() {
// From the moment this activity is leaved, clear these values
mEditDisplayName = null;
mEditSipUri = null;
mEditOnClick = false;
super.onPause();
}
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);

View file

@ -31,10 +31,10 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.SelectableAdapter; import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.ContactAvatar;
public class ContactsAdapter extends SelectableAdapter<ContactViewHolder> public class ContactsAdapter extends SelectableAdapter<ContactViewHolder>
implements SectionIndexer { implements SectionIndexer {

View file

@ -39,8 +39,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.LinphoneLinearLayoutManager;
public class ContactsFragment extends Fragment public class ContactsFragment extends Fragment
implements OnItemClickListener, implements OnItemClickListener,

View file

@ -55,7 +55,8 @@ import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
public class ContactsManager extends ContentObserver implements FriendListListener { public class ContactsManager extends ContentObserver
implements FriendListListener, LinphoneContext.CoreStartedListener {
private List<LinphoneContact> mContacts, mSipContacts; private List<LinphoneContact> mContacts, mSipContacts;
private final ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners; private final ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners;
private MagicSearch mMagicSearch; private MagicSearch mMagicSearch;
@ -79,6 +80,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
mMagicSearch = LinphoneManager.getCore().createMagicSearch(); mMagicSearch = LinphoneManager.getCore().createMagicSearch();
mMagicSearch.setLimitedSearch(false); // Do not limit the number of results mMagicSearch.setLimitedSearch(false); // Do not limit the number of results
} }
LinphoneContext.instance().addCoreStartedListener(this);
} }
public void addContactsListener(ContactsUpdatedListener listener) { public void addContactsListener(ContactsUpdatedListener listener) {
@ -104,6 +107,13 @@ public class ContactsManager extends ContentObserver implements FriendListListen
fetchContactsAsync(); fetchContactsAsync();
} }
@Override
public void onCoreStarted() {
// Core has been started, fetch contacts again in case there are some
// in the configuration file or remote provisioning
fetchContactsAsync();
}
public synchronized List<LinphoneContact> getContacts() { public synchronized List<LinphoneContact> getContacts() {
return mContacts; return mContacts;
} }
@ -122,6 +132,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
public void destroy() { public void destroy() {
mContext.getContentResolver().unregisterContentObserver(this); mContext.getContentResolver().unregisterContentObserver(this);
LinphoneContext.instance().removeCoreStartedListener(this);
if (mLoadContactTask != null) { if (mLoadContactTask != null) {
mLoadContactTask.cancel(true); mLoadContactTask.cancel(true);
@ -149,10 +160,12 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mLoadContactTask != null) { if (mLoadContactTask != null) {
mLoadContactTask.cancel(true); mLoadContactTask.cancel(true);
} }
if (!hasReadContactsAccess()) { if (!hasReadContactsAccess()) {
Log.w("[Contacts Manager] Can't fetch contact without READ permission"); Log.w(
return; "[Contacts Manager] Can't fetch native contacts without READ_CONTACTS permission");
} }
mLoadContactTask = new AsyncContactsLoader(mContext); mLoadContactTask = new AsyncContactsLoader(mContext);
mContactsFetchedOnce = true; mContactsFetchedOnce = true;
mLoadContactTask.executeOnExecutor(THREAD_POOL_EXECUTOR); mLoadContactTask.executeOnExecutor(THREAD_POOL_EXECUTOR);
@ -212,6 +225,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) { if (mContext == null) {
return false; return false;
} }
boolean contactsR = boolean contactsR =
(PackageManager.PERMISSION_GRANTED (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager() == mContext.getPackageManager()
@ -226,6 +240,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) { if (mContext == null) {
return false; return false;
} }
return (PackageManager.PERMISSION_GRANTED return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager() == mContext.getPackageManager()
.checkPermission( .checkPermission(
@ -236,6 +251,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) { if (mContext == null) {
return false; return false;
} }
return (PackageManager.PERMISSION_GRANTED return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager() == mContext.getPackageManager()
.checkPermission( .checkPermission(
@ -264,10 +280,6 @@ public class ContactsManager extends ContentObserver implements FriendListListen
} }
} }
} }
if (mContext != null && getContacts().isEmpty() && hasReadContactsAccess()) {
fetchContactsAsync();
}
} }
private void makeContactAccountVisible() { private void makeContactAccountVisible() {
@ -366,6 +378,11 @@ public class ContactsManager extends ContentObserver implements FriendListListen
} }
String username = address.getUsername(); String username = address.getUsername();
if (username == null) {
Log.w("[Contacts Manager] Address ", address.asString(), " doesn't have a username!");
return null;
}
if (android.util.Patterns.PHONE.matcher(username).matches()) { if (android.util.Patterns.PHONE.matcher(username).matches()) {
return findContactFromPhoneNumber(username); return findContactFromPhoneNumber(username);
} }
@ -421,6 +438,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
} }
public String getAddressOrNumberForAndroidContact(ContentResolver resolver, Uri contactUri) { public String getAddressOrNumberForAndroidContact(ContentResolver resolver, Uri contactUri) {
if (resolver == null || contactUri == null) return null;
// Phone Numbers // Phone Numbers
String[] projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER}; String[] projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor c = resolver.query(contactUri, projection, null, null, null); Cursor c = resolver.query(contactUri, projection, null, null, null);
@ -431,8 +450,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
c.close(); c.close();
return number; return number;
} }
}
c.close(); c.close();
}
projection = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS}; projection = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS};
c = resolver.query(contactUri, projection, null, null, null); c = resolver.query(contactUri, projection, null, null, null);
@ -444,12 +463,14 @@ public class ContactsManager extends ContentObserver implements FriendListListen
c.close(); c.close();
return address; return address;
} }
}
c.close(); c.close();
}
return null; return null;
} }
private synchronized boolean refreshSipContact(Friend lf) { private synchronized boolean refreshSipContact(Friend lf) {
if (lf == null) return false;
LinphoneContact contact = (LinphoneContact) lf.getUserData(); LinphoneContact contact = (LinphoneContact) lf.getUserData();
if (contact != null) { if (contact != null) {
@ -486,6 +507,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
ArrayList<ContentProviderOperation> ops = new ArrayList<>(); ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (String id : ids) { for (String id : ids) {
Log.i("[Contacts Manager] Adding Android contact id ", id, " to batch removal");
String[] args = new String[] {id}; String[] args = new String[] {id};
ops.add( ops.add(
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI) ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)

View file

@ -33,7 +33,6 @@ import org.linphone.core.Address;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.Friend; import org.linphone.core.Friend;
import org.linphone.core.FriendCapability; import org.linphone.core.FriendCapability;
import org.linphone.core.FriendList;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
import org.linphone.core.SubscribePolicy; import org.linphone.core.SubscribePolicy;
@ -195,7 +194,7 @@ public class LinphoneContact extends AndroidContact
} }
private void setPhotoUri(Uri uri) { private void setPhotoUri(Uri uri) {
if (uri.equals(mPhotoUri)) return; if (uri != null && uri.equals(mPhotoUri)) return;
mPhotoUri = uri; mPhotoUri = uri;
} }
@ -204,7 +203,7 @@ public class LinphoneContact extends AndroidContact
} }
private void setThumbnailUri(Uri uri) { private void setThumbnailUri(Uri uri) {
if (uri.equals(mThumbnailUri)) return; if (uri != null && uri.equals(mThumbnailUri)) return;
mThumbnailUri = uri; mThumbnailUri = uri;
} }
@ -216,14 +215,16 @@ public class LinphoneContact extends AndroidContact
if (noa == null) return; if (noa == null) return;
boolean found = false; boolean found = false;
String normalizedPhone = noa.getNormalizedPhone();
// Check for duplicated phone numbers but with different formats // Check for duplicated phone numbers but with different formats
for (LinphoneNumberOrAddress number : mAddresses) { for (LinphoneNumberOrAddress number : mAddresses) {
if (!number.isSIPAddress()) { if (!number.isSIPAddress()) {
if ((!noa.isSIPAddress() if ((!noa.isSIPAddress()
&& noa.getNormalizedPhone().equals(number.getNormalizedPhone())) && normalizedPhone != null
&& normalizedPhone.equals(number.getNormalizedPhone()))
|| (noa.isSIPAddress() || (noa.isSIPAddress()
&& noa.getValue().equals(number.getNormalizedPhone())) && noa.getValue().equals(number.getNormalizedPhone()))
|| (noa.getNormalizedPhone().equals(number.getValue()))) { || (normalizedPhone != null && normalizedPhone.equals(number.getValue()))) {
Log.d("[Linphone Contact] Duplicated entry detected: " + noa); Log.d("[Linphone Contact] Duplicated entry detected: " + noa);
found = true; found = true;
break; break;
@ -247,7 +248,8 @@ public class LinphoneContact extends AndroidContact
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) { for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
if (noa.isSIPAddress()) { if (noa.isSIPAddress()) {
String value = noa.getValue(); String value = noa.getValue();
if (address.startsWith(value) || value.equals("sip:" + address)) { if (value != null
&& (address.startsWith(value) || value.equals("sip:" + address))) {
// Startswith is to workaround the fact that the // Startswith is to workaround the fact that the
// address may have a ;gruu= at the end... // address may have a ;gruu= at the end...
return true; return true;
@ -274,7 +276,8 @@ public class LinphoneContact extends AndroidContact
} }
LinphoneNumberOrAddress toRemove = null; LinphoneNumberOrAddress toRemove = null;
for (LinphoneNumberOrAddress address : mAddresses) { for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue()) if (noa.getOldValue() != null
&& noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) { && noa.isSIPAddress() == address.isSIPAddress()) {
toRemove = address; toRemove = address;
break; break;
@ -305,7 +308,8 @@ public class LinphoneContact extends AndroidContact
} }
} }
for (LinphoneNumberOrAddress address : mAddresses) { for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue()) if (noa.getOldValue() != null
&& noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) { && noa.isSIPAddress() == address.isSIPAddress()) {
address.setValue(noa.getValue()); address.setValue(noa.getValue());
break; break;
@ -352,10 +356,11 @@ public class LinphoneContact extends AndroidContact
if (mFriend.getVcard() != null) { if (mFriend.getVcard() != null) {
mFriend.getVcard().setFamilyName(mLastName); mFriend.getVcard().setFamilyName(mLastName);
mFriend.getVcard().setGivenName(mFirstName); mFriend.getVcard().setGivenName(mFirstName);
}
if (mOrganization != null) { if (mOrganization != null) {
mFriend.getVcard().setOrganization(mOrganization); mFriend.getVcard().setOrganization(mOrganization);
} }
}
if (!created) { if (!created) {
for (Address address : mFriend.getAddresses()) { for (Address address : mFriend.getAddresses()) {
@ -395,12 +400,12 @@ public class LinphoneContact extends AndroidContact
} }
public void deleteFriend() { public void deleteFriend() {
if (mFriend == null) return;
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
if (mFriend != null && core != null) { if (core == null) return;
for (FriendList list : core.getFriendsLists()) {
list.removeFriend(mFriend); Log.i("[Contact] Deleting friend ", mFriend.getName(), " for contact ", this);
} mFriend.remove();
}
} }
public void createOrUpdateFriendFromNativeContact() { public void createOrUpdateFriendFromNativeContact() {
@ -427,7 +432,9 @@ public class LinphoneContact extends AndroidContact
if (mFriend == null) return false; if (mFriend == null) return false;
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) { for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
PresenceModel pm = mFriend.getPresenceModelForUriOrTel(noa.getValue()); PresenceModel pm = mFriend.getPresenceModelForUriOrTel(noa.getValue());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) { if (pm != null
&& pm.getBasicStatus() != null
&& pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
return true; return true;
} }
} }
@ -449,14 +456,22 @@ public class LinphoneContact extends AndroidContact
} }
public boolean hasPresenceModelForUriOrTelCapability(String uri, FriendCapability capability) { public boolean hasPresenceModelForUriOrTelCapability(String uri, FriendCapability capability) {
if (mFriend == null) return false; if (mFriend == null || uri == null) return false;
if (mFriend.getPresenceModelForUriOrTel(uri) != null) {
return mFriend.getPresenceModelForUriOrTel(uri).hasCapability(capability); PresenceModel presence = mFriend.getPresenceModelForUriOrTel(uri);
if (presence != null) {
return presence.hasCapability(capability);
} else { } else {
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) { for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
if (getContactFromPresenceModelForUriOrTel(noa.getValue()).equals(uri)) { String value = noa.getValue();
return mFriend.getPresenceModelForUriOrTel(noa.getValue()) if (value != null) {
.hasCapability(capability); String contact = getContactFromPresenceModelForUriOrTel(value);
if (contact != null && contact.equals(uri)) {
presence = mFriend.getPresenceModelForUriOrTel(value);
if (presence != null) {
return presence.hasCapability(capability);
}
}
} }
} }
} }
@ -547,12 +562,18 @@ public class LinphoneContact extends AndroidContact
String data3 = c.getString(c.getColumnIndex("data3")); String data3 = c.getString(c.getColumnIndex("data3"));
String data4 = c.getString(c.getColumnIndex("data4")); String data4 = c.getString(c.getColumnIndex("data4"));
if (getFullName() == null) { String fullName = getFullName();
if (fullName == null || !fullName.equals(displayName)) {
Log.d("[Linphone Contact] Setting display name " + displayName); Log.d("[Linphone Contact] Setting display name " + displayName);
setFullName(displayName); setFullName(displayName);
} }
if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mime)) { if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mime)) {
if (data1 == null && data4 == null) {
Log.e("[Linphone Contact] Phone number data are both null !");
return;
}
Log.d("[Linphone Contact] Found phone number " + data1 + " (" + data4 + ")"); Log.d("[Linphone Contact] Found phone number " + data1 + " (" + data4 + ")");
addNumberOrAddress(new LinphoneNumberOrAddress(data1, data4)); addNumberOrAddress(new LinphoneNumberOrAddress(data1, data4));
} else if (ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE.equals(mime) } else if (ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE.equals(mime)
@ -560,12 +581,27 @@ public class LinphoneContact extends AndroidContact
.getApplicationContext() .getApplicationContext()
.getString(R.string.linphone_address_mime_type) .getString(R.string.linphone_address_mime_type)
.equals(mime)) { .equals(mime)) {
if (data1 == null) {
Log.e("[Linphone Contact] SIP address is null !");
return;
}
Log.d("[Linphone Contact] Found SIP address " + data1); Log.d("[Linphone Contact] Found SIP address " + data1);
addNumberOrAddress(new LinphoneNumberOrAddress(data1, true)); addNumberOrAddress(new LinphoneNumberOrAddress(data1, true));
} else if (ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE.equals(mime)) { } else if (ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE.equals(mime)) {
if (data1 == null) {
Log.e("[Linphone Contact] Organization is null !");
return;
}
Log.d("[Linphone Contact] Found organization " + data1); Log.d("[Linphone Contact] Found organization " + data1);
setOrganization(data1, false); setOrganization(data1, false);
} else if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mime)) { } else if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mime)) {
if (data2 == null && data3 == null) {
Log.e("[Linphone Contact] Firstname and lastname are both null !");
return;
}
Log.d("[Linphone Contact] Found first name " + data2 + " and last name " + data3); Log.d("[Linphone Contact] Found first name " + data2 + " and last name " + data3);
setFirstNameAndLastName(data2, data3, false); setFirstNameAndLastName(data2, data3, false);
} else { } else {
@ -592,7 +628,9 @@ public class LinphoneContact extends AndroidContact
// Test presence of the value // Test presence of the value
PresenceModel pm = getFriend().getPresenceModelForUriOrTel(value); PresenceModel pm = getFriend().getPresenceModelForUriOrTel(value);
// If presence is not null // If presence is not null
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) { if (pm != null
&& pm.getBasicStatus() != null
&& pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
Log.d("[Contact] Found presence information for phone number " + value); Log.d("[Contact] Found presence information for phone number " + value);
if (!isLinphoneAddressMimeEntryAlreadyExisting(value)) { if (!isLinphoneAddressMimeEntryAlreadyExisting(value)) {
// Do the action on the contact only once if it has not been done yet // Do the action on the contact only once if it has not been done yet
@ -605,12 +643,19 @@ public class LinphoneContact extends AndroidContact
public void save() { public void save() {
saveChangesCommited(); saveChangesCommited();
if (getAndroidId() != null) {
setThumbnailUri(getContactThumbnailPictureUri());
setPhotoUri(getContactPictureUri());
}
syncValuesFromAndroidContact(LinphoneContext.instance().getApplicationContext()); syncValuesFromAndroidContact(LinphoneContext.instance().getApplicationContext());
createOrUpdateFriend(); createOrUpdateFriend();
} }
public void delete() { public void delete() {
Log.i("[Contact] Deleting contact ", this);
if (isAndroidContact()) {
deleteAndroidContact(); deleteAndroidContact();
}
if (isFriend()) { if (isFriend()) {
deleteFriend(); deleteFriend();
} }

View file

@ -54,9 +54,11 @@ public class SearchContactViewHolder extends RecyclerView.ViewHolder
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (mListener != null) { if (mListener != null) {
if (disabled.getVisibility() == View.GONE) {
mListener.onItemClicked(getAdapterPosition()); mListener.onItemClicked(getAdapterPosition());
} }
} }
}
public interface ClickListener { public interface ClickListener {
void onItemClicked(int position); void onItemClicked(int position);

View file

@ -30,13 +30,14 @@ import java.util.Objects;
import org.linphone.LinphoneContext; import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Core;
import org.linphone.core.FriendCapability; import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus; import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel; import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.core.SearchResult; import org.linphone.core.SearchResult;
import org.linphone.views.ContactAvatar;
public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactViewHolder> { public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactViewHolder> {
private List<SearchResult> mContacts; private List<SearchResult> mContacts;
@ -118,8 +119,20 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
holder.name.setText(searchResult.getAddress().getDisplayName()); holder.name.setText(searchResult.getAddress().getDisplayName());
} }
} }
holder.disabled.setVisibility(View.GONE); holder.disabled.setVisibility(View.GONE);
if (mSecurityEnabled || !mIsOnlyOnePersonSelection) {
Core core = LinphoneManager.getCore();
ProxyConfig defaultProxyConfig = core.getDefaultProxyConfig();
if (defaultProxyConfig != null) {
// SDK won't accept ourselves in the list of participants
if (defaultProxyConfig.getIdentityAddress().weakEqual(searchResult.getAddress())) {
// Disable row, we can't use our own address in a group chat room
holder.disabled.setVisibility(View.VISIBLE);
}
}
}
if (contact != null) { if (contact != null) {
if (contact.getFullName() == null if (contact.getFullName() == null
&& contact.getFirstName() == null && contact.getFirstName() == null
@ -137,15 +150,6 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
&& !searchResult.hasCapability(FriendCapability.LimeX3Dh))) { && !searchResult.hasCapability(FriendCapability.LimeX3Dh))) {
// Disable row, contact doesn't have the required capabilities // Disable row, contact doesn't have the required capabilities
holder.disabled.setVisibility(View.VISIBLE); holder.disabled.setVisibility(View.VISIBLE);
} else if (mSecurityEnabled || !mIsOnlyOnePersonSelection) {
ProxyConfig lpc =
Objects.requireNonNull(LinphoneManager.getCore()).getDefaultProxyConfig();
if (lpc != null
&& searchResult.getAddress() != null
&& lpc.getIdentityAddress().weakEqual(searchResult.getAddress())) {
// Disable row, we can't use our own address in a group chat room
holder.disabled.setVisibility(View.VISIBLE);
}
} }
} else { } else {
ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout); ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout);

View file

@ -17,49 +17,25 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.contacts.views;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.core.ChatRoomSecurityLevel; import org.linphone.core.ChatRoomSecurityLevel;
import org.linphone.utils.ImageUtils; import org.linphone.utils.ImageUtils;
class ContactAvatarHolder {
public final ImageView contactPicture;
public final ImageView avatarBorder;
public final ImageView securityLevel;
public final TextView generatedAvatar;
public final ImageView generatedAvatarBackground;
public ContactAvatarHolder(View v) {
contactPicture = v.findViewById(R.id.contact_picture);
securityLevel = v.findViewById(R.id.security_level);
generatedAvatar = v.findViewById(R.id.generated_avatar);
generatedAvatarBackground = v.findViewById(R.id.generated_avatar_background);
avatarBorder = v.findViewById(R.id.border);
}
public void init() {
contactPicture.setVisibility(View.VISIBLE);
generatedAvatar.setVisibility(View.VISIBLE);
generatedAvatarBackground.setVisibility(View.VISIBLE);
securityLevel.setVisibility(View.GONE);
avatarBorder.setVisibility(View.GONE);
}
}
public class ContactAvatar { public class ContactAvatar {
private static String generateAvatar(String displayName) { private static String generateAvatar(String displayName) {
String[] names = displayName.split(" "); String[] names = displayName.split(" ");
StringBuilder generatedAvatarText = new StringBuilder(); StringBuilder generatedAvatarText = new StringBuilder();
int count = 0;
for (String name : names) { for (String name : names) {
if (name != null && name.length() > 0) { if (name != null && name.length() > 0 && count < 2) {
generatedAvatarText.append(name.charAt(0)); generatedAvatarText.append(name.charAt(0));
count += 1;
} }
} }
return generatedAvatarText.toString().toUpperCase(); return generatedAvatarText.toString().toUpperCase();
@ -161,10 +137,7 @@ public class ContactAvatar {
Bitmap bm = ImageUtils.getRoundBitmapFromUri(v.getContext(), contact.getThumbnailUri()); Bitmap bm = ImageUtils.getRoundBitmapFromUri(v.getContext(), contact.getThumbnailUri());
if (bm != null) { if (bm != null) {
holder.contactPicture.setImageBitmap(bm); displayAvatar(bm, holder);
holder.contactPicture.setVisibility(View.VISIBLE);
holder.generatedAvatar.setVisibility(View.GONE);
holder.generatedAvatarBackground.setVisibility(View.GONE);
} else if (generated_avatars) { } else if (generated_avatars) {
holder.generatedAvatar.setVisibility(View.VISIBLE); holder.generatedAvatar.setVisibility(View.VISIBLE);
holder.generatedAvatarBackground.setVisibility(View.VISIBLE); holder.generatedAvatarBackground.setVisibility(View.VISIBLE);
@ -175,6 +148,27 @@ public class ContactAvatar {
} }
} }
private static void displayAvatar(Bitmap bm, ContactAvatarHolder holder) {
holder.contactPicture.setImageBitmap(bm);
holder.contactPicture.setVisibility(View.VISIBLE);
holder.generatedAvatar.setVisibility(View.GONE);
holder.generatedAvatarBackground.setVisibility(View.GONE);
}
public static void displayAvatar(Bitmap bm, View v) {
if (bm == null || v == null) return;
ContactAvatarHolder holder = new ContactAvatarHolder(v);
holder.init();
holder.generatedAvatar.setVisibility(View.GONE);
holder.generatedAvatarBackground.setVisibility(View.GONE);
holder.contactPicture.setVisibility(View.VISIBLE);
holder.securityLevel.setVisibility(View.GONE);
displayAvatar(bm, holder);
}
public static void displayAvatar(LinphoneContact contact, View v) { public static void displayAvatar(LinphoneContact contact, View v) {
displayAvatar(contact, v, false); displayAvatar(contact, v, false);
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.contacts.views;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.linphone.R;
class ContactAvatarHolder {
public final ImageView contactPicture;
public final ImageView avatarBorder;
public final ImageView securityLevel;
public final TextView generatedAvatar;
public final ImageView generatedAvatarBackground;
public ContactAvatarHolder(View v) {
contactPicture = v.findViewById(R.id.contact_picture);
securityLevel = v.findViewById(R.id.security_level);
generatedAvatar = v.findViewById(R.id.generated_avatar);
generatedAvatarBackground = v.findViewById(R.id.generated_avatar_background);
avatarBorder = v.findViewById(R.id.border);
}
public void init() {
contactPicture.setVisibility(View.VISIBLE);
generatedAvatar.setVisibility(View.VISIBLE);
generatedAvatarBackground.setVisibility(View.VISIBLE);
securityLevel.setVisibility(View.GONE);
avatarBorder.setVisibility(View.GONE);
}
}

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.contacts.views;
import android.content.Context; import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.activities; package org.linphone.dialer;
import android.Manifest; import android.Manifest;
import android.content.Intent; import android.content.Intent;
@ -37,17 +37,18 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.MainActivity;
import org.linphone.call.views.CallButton;
import org.linphone.contacts.ContactsActivity; import org.linphone.contacts.ContactsActivity;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.views.AddressText;
import org.linphone.dialer.views.Digit;
import org.linphone.dialer.views.EraseButton;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.views.AddressText;
import org.linphone.views.CallButton;
import org.linphone.views.Digit;
import org.linphone.views.EraseButton;
public class DialerActivity extends MainActivity implements AddressText.AddressChangedListener { public class DialerActivity extends MainActivity implements AddressText.AddressChangedListener {
private static final String ACTION_CALL_LINPHONE = "org.linphone.intent.action.CallLaunched"; private static final String ACTION_CALL_LINPHONE = "org.linphone.intent.action.CallLaunched";
@ -220,14 +221,15 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
private void enableVideoPreviewIfTablet(boolean enable) { private void enableVideoPreviewIfTablet(boolean enable) {
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
TextureView preview = findViewById(R.id.video_preview); TextureView preview = findViewById(R.id.video_preview);
if (preview != null && core != null) { ImageView changeCamera = findViewById(R.id.video_preview_change_camera);
if (preview != null && changeCamera != null && core != null) {
if (enable && isTablet() && LinphonePreferences.instance().isVideoPreviewEnabled()) { if (enable && isTablet() && LinphonePreferences.instance().isVideoPreviewEnabled()) {
preview.setVisibility(View.VISIBLE); preview.setVisibility(View.VISIBLE);
core.setNativePreviewWindowId(preview); core.setNativePreviewWindowId(preview);
core.enableVideoPreview(true); core.enableVideoPreview(true);
ImageView changeCamera = findViewById(R.id.video_preview_change_camera); if (core.getVideoDevicesList().length > 1) {
if (changeCamera != null && core.getVideoDevicesList().length > 1) {
changeCamera.setVisibility(View.VISIBLE); changeCamera.setVisibility(View.VISIBLE);
changeCamera.setOnClickListener( changeCamera.setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@ -239,6 +241,7 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
} }
} else { } else {
preview.setVisibility(View.GONE); preview.setVisibility(View.GONE);
changeCamera.setVisibility(View.GONE);
core.setNativePreviewWindowId(null); core.setNativePreviewWindowId(null);
core.enableVideoPreview(false); core.enableVideoPreview(false);
} }
@ -259,9 +262,7 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
@Override @Override
public void onAddressChanged() { public void onAddressChanged() {
Core core = LinphoneManager.getCore(); mAddContact.setEnabled(!mAddress.getText().toString().isEmpty());
mAddContact.setEnabled(
core != null && core.getCallsNb() > 0 || !mAddress.getText().toString().equals(""));
} }
private void updateLayout() { private void updateLayout() {
@ -273,6 +274,8 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
boolean atLeastOneCall = core.getCallsNb() > 0; boolean atLeastOneCall = core.getCallsNb() > 0;
mStartCall.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE); mStartCall.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE);
mAddContact.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE); mAddContact.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE);
mAddContact.setEnabled(!mAddress.getText().toString().isEmpty());
if (!atLeastOneCall) { if (!atLeastOneCall) {
if (core.getVideoActivationPolicy().getAutomaticallyInitiate()) { if (core.getVideoActivationPolicy().getAutomaticallyInitiate()) {
mStartCall.setImageResource(R.drawable.call_video_start); mStartCall.setImageResource(R.drawable.call_video_start);

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.dialer.views;
public interface AddressAware { public interface AddressAware {
void setAddressWidget(AddressText address); void setAddressWidget(AddressText address);

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.dialer.views;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.dialer.views;
public interface AddressType { public interface AddressType {
CharSequence getText(); CharSequence getText();

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.dialer.views;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.views; package org.linphone.dialer.views;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;

View file

@ -37,7 +37,7 @@ public class FirebaseMessaging extends FirebaseMessagingService {
android.util.Log.i( android.util.Log.i(
"FirebaseMessaging", "[Push Notification] Starting context"); "FirebaseMessaging", "[Push Notification] Starting context");
new LinphoneContext(getApplicationContext()); new LinphoneContext(getApplicationContext());
LinphoneContext.instance().start(false); LinphoneContext.instance().start(true);
} else { } else {
Log.i("[Push Notification] Notifying Core"); Log.i("[Push Notification] Notifying Core");
if (LinphoneManager.getInstance() != null) { if (LinphoneManager.getInstance() != null) {

View file

@ -79,10 +79,8 @@ public class StatusBarFragment extends Fragment {
final RegistrationState state, final RegistrationState state,
String smessage) { String smessage) {
if (core.getProxyConfigList() == null) { if (core.getProxyConfigList() == null) {
mStatusLed.setImageResource(R.drawable.led_disconnected); showNoAccountConfigured();
mStatusText.setText(getString(R.string.no_account)); return;
} else {
mStatusLed.setVisibility(View.VISIBLE);
} }
if ((core.getDefaultProxyConfig() != null if ((core.getDefaultProxyConfig() != null
@ -152,6 +150,8 @@ public class StatusBarFragment extends Fragment {
ProxyConfig lpc = core.getDefaultProxyConfig(); ProxyConfig lpc = core.getDefaultProxyConfig();
if (lpc != null) { if (lpc != null) {
mListener.onRegistrationStateChanged(core, lpc, lpc.getState(), null); mListener.onRegistrationStateChanged(core, lpc, lpc.getState(), null);
} else {
showNoAccountConfigured();
} }
} else { } else {
mStatusText.setVisibility(View.VISIBLE); mStatusText.setVisibility(View.VISIBLE);
@ -178,11 +178,15 @@ public class StatusBarFragment extends Fragment {
mVoicemailCount.setVisibility(View.VISIBLE); mVoicemailCount.setVisibility(View.VISIBLE);
if (core.getProxyConfigList().length == 0) { if (core.getProxyConfigList().length == 0) {
showNoAccountConfigured();
}
}
}
private void showNoAccountConfigured() {
mStatusLed.setImageResource(R.drawable.led_disconnected); mStatusLed.setImageResource(R.drawable.led_disconnected);
mStatusText.setText(getString(R.string.no_account)); mStatusText.setText(getString(R.string.no_account));
} }
}
}
private int getStatusIconResource(RegistrationState state) { private int getStatusIconResource(RegistrationState state) {
try { try {

View file

@ -23,6 +23,7 @@ import android.app.Fragment;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.MainActivity; import org.linphone.activities.MainActivity;
@ -71,6 +72,7 @@ public class HistoryActivity extends MainActivity {
mHistorySelected.setVisibility(View.VISIBLE); mHistorySelected.setVisibility(View.VISIBLE);
LinphoneManager.getCore().resetMissedCallsCount(); LinphoneManager.getCore().resetMissedCallsCount();
displayMissedCalls(); displayMissedCalls();
LinphoneContext.instance().getNotificationManager().dismissMissedCallNotification();
} }
@Override @Override

View file

@ -30,13 +30,13 @@ import java.util.List;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.CallLog; import org.linphone.core.CallLog;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableAdapter; import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.ContactAvatar;
public class HistoryAdapter extends SelectableAdapter<HistoryViewHolder> { public class HistoryAdapter extends SelectableAdapter<HistoryViewHolder> {
private final List<CallLog> mLogs; private final List<CallLog> mLogs;

View file

@ -35,20 +35,22 @@ import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog; import org.linphone.core.CallLog;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend; import org.linphone.core.ChatRoomBackend;
import org.linphone.core.ChatRoomListenerStub; import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams; import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.FriendCapability; import org.linphone.core.FriendCapability;
import org.linphone.core.ProxyConfig; import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class HistoryDetailFragment extends Fragment { public class HistoryDetailFragment extends Fragment {
private ImageView mAddToContacts; private ImageView mAddToContacts;
@ -60,6 +62,7 @@ public class HistoryDetailFragment extends Fragment {
private ChatRoom mChatRoom; private ChatRoom mChatRoom;
private ChatRoomListenerStub mChatRoomCreationListener; private ChatRoomListenerStub mChatRoomCreationListener;
private ListView mLogsList; private ListView mLogsList;
private CoreListenerStub mListener;
@Override @Override
public View onCreateView( public View onCreateView(
@ -157,7 +160,7 @@ public class HistoryDetailFragment extends Fragment {
mWaitLayout.setVisibility(View.GONE); mWaitLayout.setVisibility(View.GONE);
((HistoryActivity) getActivity()).displayChatRoomError(); ((HistoryActivity) getActivity()).displayChatRoomError();
Log.e( Log.e(
"Group mChat room for address " "[History Detail Fragment] Group mChat room for address "
+ cr.getPeerAddress() + cr.getPeerAddress()
+ " has failed !"); + " has failed !");
} }
@ -165,16 +168,36 @@ public class HistoryDetailFragment extends Fragment {
}; };
mLogsList = view.findViewById(R.id.logs_list); mLogsList = view.findViewById(R.id.logs_list);
mListener =
new CoreListenerStub() {
@Override
public void onCallStateChanged(
Core core, Call call, Call.State state, String message) {
if (state == Call.State.End || state == Call.State.Error) {
displayHistory(); displayHistory();
}
}
};
return view; return view;
} }
@Override
public void onResume() {
super.onResume();
LinphoneManager.getCore().addListener(mListener);
displayHistory();
}
@Override @Override
public void onPause() { public void onPause() {
if (mChatRoom != null) { if (mChatRoom != null) {
mChatRoom.removeListener(mChatRoomCreationListener); mChatRoom.removeListener(mChatRoomCreationListener);
} }
LinphoneManager.getCore().removeListener(mListener);
super.onPause(); super.onPause();
} }
@ -234,24 +257,29 @@ public class HistoryDetailFragment extends Fragment {
private void goToChat(boolean isSecured) { private void goToChat(boolean isSecured) {
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
if (core == null) return;
Address participant = Factory.instance().createAddress(mSipUri); Address participant = Factory.instance().createAddress(mSipUri);
ProxyConfig defaultProxyConfig = core.getDefaultProxyConfig();
if (defaultProxyConfig != null) {
ChatRoom room = ChatRoom room =
core.findOneToOneChatRoom( core.findOneToOneChatRoom(
core.getDefaultProxyConfig().getContact(), participant, isSecured); defaultProxyConfig.getContact(), participant, isSecured);
if (room != null) { if (room != null) {
((HistoryActivity) getActivity()) ((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress()); .showChatRoom(room.getLocalAddress(), room.getPeerAddress());
} else { } else {
ProxyConfig lpc = core.getDefaultProxyConfig(); if (defaultProxyConfig.getConferenceFactoryUri() != null
if (lpc != null && (isSecured
&& lpc.getConferenceFactoryUri() != null || !LinphonePreferences.instance().useBasicChatRoomFor1To1())) {
&& (isSecured || !LinphonePreferences.instance().useBasicChatRoomFor1To1())) {
mWaitLayout.setVisibility(View.VISIBLE); mWaitLayout.setVisibility(View.VISIBLE);
ChatRoomParams params = core.createDefaultChatRoomParams(); ChatRoomParams params = core.createDefaultChatRoomParams();
params.enableEncryption(isSecured); params.enableEncryption(isSecured);
params.enableGroup(false); params.enableGroup(false);
// We don't want a basic chat room // We don't want a basic chat room,
// so if isSecured is false we have to set this manually
params.setBackend(ChatRoomBackend.FlexisipChat); params.setBackend(ChatRoomBackend.FlexisipChat);
Address[] participants = new Address[1]; Address[] participants = new Address[1];
@ -259,7 +287,9 @@ public class HistoryDetailFragment extends Fragment {
mChatRoom = mChatRoom =
core.createChatRoom( core.createChatRoom(
params, getString(R.string.dummy_group_chat_subject), participants); params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) { if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener); mChatRoom.addListener(mChatRoomCreationListener);
} else { } else {
@ -274,5 +304,18 @@ public class HistoryDetailFragment extends Fragment {
} }
} }
} }
} else {
if (isSecured) {
Log.e(
"[History Detail Fragment] Can't create a secured chat room without proxy config");
return;
}
ChatRoom room = core.getChatRoom(participant);
if (room != null) {
((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
}
}
} }
} }

View file

@ -35,23 +35,26 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.CallLog; import org.linphone.core.CallLog;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.LinphoneLinearLayoutManager;
public class HistoryFragment extends Fragment public class HistoryFragment extends Fragment
implements OnClickListener, implements OnClickListener,
OnItemClickListener, OnItemClickListener,
HistoryViewHolder.ClickListener, HistoryViewHolder.ClickListener,
ContactsUpdatedListener, ContactsUpdatedListener,
SelectableHelper.DeleteListener { SelectableHelper.DeleteListener,
LinphoneContext.CoreStartedListener {
private RecyclerView mHistoryList; private RecyclerView mHistoryList;
private TextView mNoCallHistory, mNoMissedCallHistory; private TextView mNoCallHistory, mNoMissedCallHistory;
private ImageView mMissedCalls, mAllCalls; private ImageView mMissedCalls, mAllCalls;
@ -60,6 +63,7 @@ public class HistoryFragment extends Fragment
private List<CallLog> mLogs; private List<CallLog> mLogs;
private HistoryAdapter mHistoryAdapter; private HistoryAdapter mHistoryAdapter;
private SelectableHelper mSelectionHelper; private SelectableHelper mSelectionHelper;
private CoreListenerStub mListener;
@Override @Override
public View onCreateView( public View onCreateView(
@ -94,26 +98,37 @@ public class HistoryFragment extends Fragment
mAllCalls.setEnabled(false); mAllCalls.setEnabled(false);
mOnlyDisplayMissedCalls = false; mOnlyDisplayMissedCalls = false;
mListener =
new CoreListenerStub() {
@Override
public void onCallStateChanged(
Core core, Call call, Call.State state, String message) {
if (state == Call.State.End || state == Call.State.Error) {
reloadData();
}
}
};
return view; return view;
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
ContactsManager.getInstance().addContactsListener(this);
mLogs = Arrays.asList(LinphoneManager.getCore().getCallLogs()); ContactsManager.getInstance().addContactsListener(this);
hideHistoryListAndDisplayMessageIfEmpty(); LinphoneContext.instance().addCoreStartedListener(this);
mHistoryAdapter = LinphoneManager.getCore().addListener(mListener);
new HistoryAdapter((HistoryActivity) getActivity(), mLogs, this, mSelectionHelper);
mHistoryList.setAdapter(mHistoryAdapter); reloadData();
mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setDialogMessage(R.string.call_log_delete_dialog);
} }
@Override @Override
public void onPause() { public void onPause() {
ContactsManager.getInstance().removeContactsListener(this); ContactsManager.getInstance().removeContactsListener(this);
LinphoneContext.instance().removeCoreStartedListener(this);
LinphoneManager.getCore().removeListener(mListener);
super.onPause(); super.onPause();
} }
@ -125,6 +140,11 @@ public class HistoryFragment extends Fragment
} }
} }
@Override
public void onCoreStarted() {
reloadData();
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
int id = v.getId(); int id = v.getId();
@ -177,6 +197,7 @@ public class HistoryFragment extends Fragment
if (mHistoryAdapter.isEditionEnabled()) { if (mHistoryAdapter.isEditionEnabled()) {
mHistoryAdapter.toggleSelection(position); mHistoryAdapter.toggleSelection(position);
} else { } else {
if (position >= 0 && position < mLogs.size()) {
CallLog log = mLogs.get(position); CallLog log = mLogs.get(position);
Address address; Address address;
if (log.getDir() == Call.Dir.Incoming) { if (log.getDir() == Call.Dir.Incoming) {
@ -185,7 +206,9 @@ public class HistoryFragment extends Fragment
address = log.getToAddress(); address = log.getToAddress();
} }
if (address != null) { if (address != null) {
LinphoneManager.getCallManager().newOutgoingCall(address.asStringUriOnly(), null); LinphoneManager.getCallManager()
.newOutgoingCall(address.asStringUriOnly(), null);
}
} }
} }
} }
@ -218,6 +241,16 @@ public class HistoryFragment extends Fragment
} }
} }
private void reloadData() {
mLogs = Arrays.asList(LinphoneManager.getCore().getCallLogs());
hideHistoryListAndDisplayMessageIfEmpty();
mHistoryAdapter =
new HistoryAdapter((HistoryActivity) getActivity(), mLogs, this, mSelectionHelper);
mHistoryList.setAdapter(mHistoryAdapter);
mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setDialogMessage(R.string.call_log_delete_dialog);
}
private void removeNotMissedCallsFromLogs() { private void removeNotMissedCallsFromLogs() {
if (mOnlyDisplayMissedCalls) { if (mOnlyDisplayMissedCalls) {
List<CallLog> missedCalls = new ArrayList<>(); List<CallLog> missedCalls = new ArrayList<>();

View file

@ -29,12 +29,11 @@ import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.service.notification.StatusBarNotification;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.DialerActivity;
import org.linphone.call.CallActivity; import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity; import org.linphone.call.CallIncomingActivity;
import org.linphone.call.CallOutgoingActivity; import org.linphone.call.CallOutgoingActivity;
@ -53,7 +52,9 @@ import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.core.Reason; import org.linphone.core.Reason;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.history.HistoryActivity; import org.linphone.history.HistoryActivity;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.DeviceUtils; import org.linphone.utils.DeviceUtils;
import org.linphone.utils.FileUtils; import org.linphone.utils.FileUtils;
@ -84,7 +85,19 @@ public class NotificationsManager {
mCurrentChatRoomAddress = null; mCurrentChatRoomAddress = null;
mNM = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE); mNM = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
if (mContext.getResources().getBoolean(R.bool.keep_missed_call_notification_upon_restart)) {
StatusBarNotification[] notifs = Compatibility.getActiveNotifications(mNM);
if (notifs != null && notifs.length > 1) {
for (StatusBarNotification notif : notifs) {
if (notif.getId() != MISSED_CALLS_NOTIF_ID) {
dismissNotification(notif.getId());
}
}
}
} else {
mNM.cancelAll(); mNM.cancelAll();
}
mLastNotificationId = 5; // Do not conflict with hardcoded notifications ids ! mLastNotificationId = 5; // Do not conflict with hardcoded notifications ids !
@ -327,6 +340,10 @@ public class NotificationsManager {
} }
} }
public void dismissMissedCallNotification() {
dismissNotification(MISSED_CALLS_NOTIF_ID);
}
public void sendNotification(int id, Notification notif) { public void sendNotification(int id, Notification notif) {
Log.i("[Notifications Manager] Notifying " + id); Log.i("[Notifications Manager] Notifying " + id);
mNM.notify(id, notif); mNM.notify(id, notif);
@ -521,7 +538,8 @@ public class NotificationsManager {
mContext, mContext,
mContext.getString(R.string.missed_calls_notif_title), mContext.getString(R.string.missed_calls_notif_title),
body, body,
pendingIntent); pendingIntent,
missedCallCount);
sendNotification(MISSED_CALLS_NOTIF_ID, notif); sendNotification(MISSED_CALLS_NOTIF_ID, notif);
} }

View file

@ -19,6 +19,7 @@
*/ */
package org.linphone.receivers; package org.linphone.receivers;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@ -28,10 +29,40 @@ import org.linphone.LinphoneManager;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
public class BluetoothReceiver extends BroadcastReceiver { public class BluetoothReceiver extends BroadcastReceiver {
public BluetoothReceiver() {
super();
Log.i("[Bluetooth] Bluetooth receiver created");
}
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { Log.i("[Bluetooth] Bluetooth broadcast received");
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
Log.w("[Bluetooth] Adapter has been turned off");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.w("[Bluetooth] Adapter is being turned off");
break;
case BluetoothAdapter.STATE_ON:
Log.i("[Bluetooth] Adapter has been turned on");
LinphoneManager.getAudioManager().bluetoothAdapterStateChanged();
break;
case BluetoothAdapter.STATE_TURNING_ON:
Log.i("[Bluetooth] Adapter is being turned on");
break;
case BluetoothAdapter.ERROR:
Log.e("[Bluetooth] Adapter is in error state !");
break;
default:
Log.w("[Bluetooth] Unknown adapter state: ", state);
break;
}
} else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
int state = int state =
intent.getIntExtra( intent.getIntExtra(
BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
@ -55,6 +86,8 @@ public class BluetoothReceiver extends BroadcastReceiver {
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
Log.i("[Bluetooth] Bluetooth headset audio disconnected"); Log.i("[Bluetooth] Bluetooth headset audio disconnected");
LinphoneManager.getAudioManager().bluetoothHeadetAudioConnectionChanged(false); LinphoneManager.getAudioManager().bluetoothHeadetAudioConnectionChanged(false);
} else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
Log.i("[Bluetooth] Bluetooth headset audio connecting");
} else { } else {
Log.w("[Bluetooth] Bluetooth headset unknown audio state changed: " + state); Log.w("[Bluetooth] Bluetooth headset unknown audio state changed: " + state);
} }

View file

@ -22,8 +22,8 @@ package org.linphone.receivers;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.linphone.LinphoneService;
import org.linphone.compatibility.Compatibility; import org.linphone.compatibility.Compatibility;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
@ -36,17 +36,34 @@ public class BootReceiver extends BroadcastReceiver {
"[Boot Receiver] Device is shutting down, destroying Core to unregister"); "[Boot Receiver] Device is shutting down, destroying Core to unregister");
context.stopService( context.stopService(
new Intent(Intent.ACTION_MAIN).setClass(context, LinphoneService.class)); new Intent(Intent.ACTION_MAIN).setClass(context, LinphoneService.class));
} else { } else if (intent.getAction().equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
LinphonePreferences.instance().setContext(context); LinphonePreferences.instance().setContext(context);
boolean autostart = LinphonePreferences.instance().isAutoStartEnabled(); boolean autostart = LinphonePreferences.instance().isAutoStartEnabled();
android.util.Log.i( android.util.Log.i(
"Linphone", "[Boot Receiver] Device is starting, auto_start is " + autostart); "Linphone", "[Boot Receiver] Device is starting, auto_start is " + autostart);
if (autostart && !LinphoneService.isReady()) { if (autostart && !LinphoneService.isReady()) {
startService(context);
}
} else if (intent.getAction().equalsIgnoreCase(Intent.ACTION_MY_PACKAGE_REPLACED)) {
LinphonePreferences.instance().setContext(context);
boolean foregroundService =
LinphonePreferences.instance().getServiceNotificationVisibility();
android.util.Log.i(
"Linphone",
"[Boot Receiver] App has been updated, foreground service is "
+ foregroundService);
if (foregroundService && !LinphoneService.isReady()) {
startService(context);
}
}
}
private void startService(Context context) {
Intent serviceIntent = new Intent(Intent.ACTION_MAIN); Intent serviceIntent = new Intent(Intent.ACTION_MAIN);
serviceIntent.setClass(context, LinphoneService.class); serviceIntent.setClass(context, LinphoneService.class);
serviceIntent.putExtra("ForceStartForeground", true); serviceIntent.putExtra("ForceStartForeground", true);
Compatibility.startService(context, serviceIntent); Compatibility.startService(context, serviceIntent);
} }
} }
}
}

View file

@ -29,9 +29,15 @@ import org.linphone.core.tools.Log;
public class HeadsetReceiver extends BroadcastReceiver { public class HeadsetReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (isInitialStickyBroadcast()) {
Log.i("[Headset] Received broadcast from sticky cache, ignoring...");
return;
}
String action = intent.getAction(); String action = intent.getAction();
if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) { if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) {
// This happens when the user plugs a Jack headset to the device for example // This happens when the user plugs a Jack headset to the device for example
// https://developer.android.com/reference/android/media/AudioManager.html#ACTION_HEADSET_PLUG
int state = intent.getIntExtra("state", 0); int state = intent.getIntExtra("state", 0);
String name = intent.getStringExtra("name"); String name = intent.getStringExtra("name");
int hasMicrophone = intent.getIntExtra("microphone", 0); int hasMicrophone = intent.getIntExtra("microphone", 0);
@ -44,7 +50,7 @@ public class HeadsetReceiver extends BroadcastReceiver {
Log.i("[Headset] Headset " + name + " has a microphone"); Log.i("[Headset] Headset " + name + " has a microphone");
} }
} else { } else {
Log.w("[Headset] Unknow headset plugged state: " + state); Log.w("[Headset] Unknown headset plugged state: " + state);
} }
LinphoneManager.getAudioManager().routeAudioToEarPiece(); LinphoneManager.getAudioManager().routeAudioToEarPiece();
@ -55,7 +61,7 @@ public class HeadsetReceiver extends BroadcastReceiver {
LinphoneManager.getAudioManager().routeAudioToEarPiece(); LinphoneManager.getAudioManager().routeAudioToEarPiece();
LinphoneManager.getCallManager().refreshInCallActions(); LinphoneManager.getCallManager().refreshInCallActions();
} else { } else {
Log.w("[Headset] Unknow action: " + action); Log.w("[Headset] Unknown action: " + action);
} }
} }
} }

View file

@ -37,9 +37,9 @@ import java.util.List;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.MainActivity; import org.linphone.activities.MainActivity;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.utils.FileUtils; import org.linphone.utils.FileUtils;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import org.linphone.views.LinphoneLinearLayoutManager;
public class RecordingsActivity extends MainActivity public class RecordingsActivity extends MainActivity
implements SelectableHelper.DeleteListener, RecordingViewHolder.ClickListener { implements SelectableHelper.DeleteListener, RecordingViewHolder.ClickListener {

View file

@ -17,15 +17,15 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.utils; package org.linphone.service;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.os.Bundle; import android.os.Bundle;
import java.util.ArrayList; import java.util.ArrayList;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.utils.LinphoneUtils;
/** /**
* Believe me or not, but knowing the application visibility state on Android is a nightmare. After * Believe me or not, but knowing the application visibility state on Android is a nightmare. After

View file

@ -17,22 +17,24 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone; package org.linphone.service;
import android.app.Application; import android.app.Application;
import android.app.Service; import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.view.WindowManager; import android.view.WindowManager;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.call.views.LinphoneGL2JNIViewOverlay;
import org.linphone.call.views.LinphoneOverlay;
import org.linphone.call.views.LinphoneTextureViewOverlay;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.tools.Log; import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version; import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences; import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.ActivityMonitor;
import org.linphone.views.LinphoneGL2JNIViewOverlay;
import org.linphone.views.LinphoneOverlay;
import org.linphone.views.LinphoneTextureViewOverlay;
public final class LinphoneService extends Service { public final class LinphoneService extends Service {
private static LinphoneService sInstance; private static LinphoneService sInstance;
@ -75,8 +77,12 @@ public final class LinphoneService extends Service {
} }
sInstance = this; sInstance = this;
if (LinphonePreferences.instance().getServiceNotificationVisibility()) { if (LinphonePreferences.instance().getServiceNotificationVisibility()
|| (Version.sdkAboveOrEqual(Version.API26_O_80)
&& intent != null
&& intent.getBooleanExtra("ForceStartForeground", false))) {
Log.i("[Service] Background service mode enabled, displaying notification"); Log.i("[Service] Background service mode enabled, displaying notification");
// We need to call this asap after the Service can be accessed through it's singleton
LinphoneContext.instance().getNotificationManager().startForeground(); LinphoneContext.instance().getNotificationManager().startForeground();
} }
@ -86,14 +92,7 @@ public final class LinphoneService extends Service {
LinphoneContext.instance().updateContext(this); LinphoneContext.instance().updateContext(this);
} }
if (Version.sdkAboveOrEqual(Version.API26_O_80)
&& intent != null
&& intent.getBooleanExtra("ForceStartForeground", false)) {
// We need to call this asap after the Service can be accessed through it's singleton
LinphoneContext.instance().getNotificationManager().startForeground();
}
Log.i("[Service] Started"); Log.i("[Service] Started");
return START_STICKY; return START_STICKY;
} }

View file

@ -17,9 +17,9 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.utils; package org.linphone.service;
import org.linphone.LinphoneService; import org.linphone.utils.LinphoneUtils;
public class ServiceWaitThread extends Thread { public class ServiceWaitThread extends Thread {
private ServiceWaitThreadListener mListener; private ServiceWaitThreadListener mListener;

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.linphone.utils; package org.linphone.service;
public interface ServiceWaitThreadListener { public interface ServiceWaitThreadListener {
void onServiceReady(); void onServiceReady();

View file

@ -240,6 +240,9 @@ public class AccountSettingsFragment extends SettingsFragment {
if (mAuthInfo != null) { if (mAuthInfo != null) {
mAuthInfo.setHa1(null); mAuthInfo.setHa1(null);
mAuthInfo.setPassword(newValue); mAuthInfo.setPassword(newValue);
// Reset algorithm to generate correct hash depending on
// algorithm set in next to come 401
mAuthInfo.setAlgorithm(null);
Core core = LinphoneManager.getCore(); Core core = LinphoneManager.getCore();
if (core != null) { if (core != null) {
core.addAuthInfo(mAuthInfo); core.addAuthInfo(mAuthInfo);

View file

@ -83,6 +83,8 @@ public class AdvancedSettingsFragment extends SettingsFragment {
mStartAtBoot = mRootView.findViewById(R.id.pref_autostart); mStartAtBoot = mRootView.findViewById(R.id.pref_autostart);
mDarkMode = mRootView.findViewById(R.id.pref_dark_mode); mDarkMode = mRootView.findViewById(R.id.pref_dark_mode);
mDarkMode.setVisibility(
getResources().getBoolean(R.bool.allow_dark_mode) ? View.VISIBLE : View.GONE);
mRemoteProvisioningUrl = mRootView.findViewById(R.id.pref_remote_provisioning); mRemoteProvisioningUrl = mRootView.findViewById(R.id.pref_remote_provisioning);
mRemoteProvisioningUrl.setInputType( mRemoteProvisioningUrl.setInputType(

View file

@ -20,9 +20,12 @@
package org.linphone.settings; package org.linphone.settings;
import android.Manifest; import android.Manifest;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -54,7 +57,7 @@ public class CallSettingsFragment extends SettingsFragment {
mAutoAnswer; mAutoAnswer;
private ListSetting mMediaEncryption; private ListSetting mMediaEncryption;
private TextSetting mAutoAnswerTime, mIncomingCallTimeout, mVoiceMailUri; private TextSetting mAutoAnswerTime, mIncomingCallTimeout, mVoiceMailUri;
private BasicSetting mDndPermissionSettings; private BasicSetting mDndPermissionSettings, mAndroidNotificationSettings;
@Nullable @Nullable
@Override @Override
@ -90,6 +93,8 @@ public class CallSettingsFragment extends SettingsFragment {
mMediaEncryption = mRootView.findViewById(R.id.pref_media_encryption); mMediaEncryption = mRootView.findViewById(R.id.pref_media_encryption);
initMediaEncryptionList(); initMediaEncryptionList();
mAndroidNotificationSettings = mRootView.findViewById(R.id.pref_android_app_notif_settings);
mAutoAnswerTime = mRootView.findViewById(R.id.pref_auto_answer_time); mAutoAnswerTime = mRootView.findViewById(R.id.pref_auto_answer_time);
mAutoAnswerTime.setInputType(InputType.TYPE_CLASS_NUMBER); mAutoAnswerTime.setInputType(InputType.TYPE_CLASS_NUMBER);
@ -167,8 +172,15 @@ public class CallSettingsFragment extends SettingsFragment {
@Override @Override
public void onListValueChanged(int position, String newLabel, String newValue) { public void onListValueChanged(int position, String newLabel, String newValue) {
try { try {
mPrefs.setMediaEncryption( MediaEncryption encryption =
MediaEncryption.fromInt(Integer.parseInt(newValue))); MediaEncryption.fromInt(Integer.parseInt(newValue));
mPrefs.setMediaEncryption(encryption);
if (encryption == MediaEncryption.None) {
mMediaEncryptionMandatory.setChecked(false);
}
mMediaEncryptionMandatory.setEnabled(
encryption != MediaEncryption.None);
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
Log.e(nfe); Log.e(nfe);
} }
@ -223,6 +235,27 @@ public class CallSettingsFragment extends SettingsFragment {
mPrefs.setMediaEncryptionMandatory(newValue); mPrefs.setMediaEncryptionMandatory(newValue);
} }
}); });
mAndroidNotificationSettings.setListener(
new SettingListenerBase() {
@Override
public void onClicked() {
if (Build.VERSION.SDK_INT >= Version.API26_O_80) {
Context context = getActivity();
Intent i = new Intent();
i.setAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
i.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
i.putExtra(
Settings.EXTRA_CHANNEL_ID,
context.getString(R.string.notification_service_channel_id));
i.addCategory(Intent.CATEGORY_DEFAULT);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(i);
}
}
});
} }
private void updateValues() { private void updateValues() {
@ -248,7 +281,12 @@ public class CallSettingsFragment extends SettingsFragment {
mDndPermissionSettings.setVisibility( mDndPermissionSettings.setVisibility(
Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60) ? View.VISIBLE : View.GONE); Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60) ? View.VISIBLE : View.GONE);
mMediaEncryptionMandatory.setChecked(mPrefs.acceptMediaEncryptionMandatory()); mMediaEncryptionMandatory.setChecked(mPrefs.isMediaEncryptionMandatory());
mMediaEncryptionMandatory.setEnabled(mPrefs.getMediaEncryption() != MediaEncryption.None);
if (Version.sdkStrictlyBelow(Version.API26_O_80)) {
mAndroidNotificationSettings.setVisibility(View.GONE);
}
setListeners(); setListeners();
} }

View file

@ -320,10 +320,12 @@ public class LinphonePreferences {
// Video settings // Video settings
public boolean useFrontCam() { public boolean useFrontCam() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "front_camera_default", true); return getConfig().getBool("app", "front_camera_default", true);
} }
public void setFrontCamAsDefault(boolean frontcam) { public void setFrontCamAsDefault(boolean frontcam) {
if (getConfig() == null) return;
getConfig().setBool("app", "front_camera_default", frontcam); getConfig().setBool("app", "front_camera_default", frontcam);
} }
@ -425,6 +427,7 @@ public class LinphonePreferences {
// Contact settings // Contact settings
public boolean isFriendlistsubscriptionEnabled() { public boolean isFriendlistsubscriptionEnabled() {
if (getConfig() == null) return false;
if (getConfig().getBool("app", "friendlist_subscription_enabled", false)) { if (getConfig().getBool("app", "friendlist_subscription_enabled", false)) {
// Old setting, do migration // Old setting, do migration
getConfig().setBool("app", "friendlist_subscription_enabled", false); getConfig().setBool("app", "friendlist_subscription_enabled", false);
@ -434,18 +437,22 @@ public class LinphonePreferences {
} }
public void enabledFriendlistSubscription(boolean enabled) { public void enabledFriendlistSubscription(boolean enabled) {
if (getLc() == null) return;
getLc().enableFriendListSubscription(enabled); getLc().enableFriendListSubscription(enabled);
} }
public boolean isPresenceStorageInNativeAndroidContactEnabled() { public boolean isPresenceStorageInNativeAndroidContactEnabled() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "store_presence_in_native_contact", false); return getConfig().getBool("app", "store_presence_in_native_contact", false);
} }
public void enabledPresenceStorageInNativeAndroidContact(boolean enabled) { public void enabledPresenceStorageInNativeAndroidContact(boolean enabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "store_presence_in_native_contact", enabled); getConfig().setBool("app", "store_presence_in_native_contact", enabled);
} }
public boolean isDisplayContactOrganization() { public boolean isDisplayContactOrganization() {
if (getConfig() == null) return false;
return getConfig() return getConfig()
.getBool( .getBool(
"app", "app",
@ -454,24 +461,29 @@ public class LinphonePreferences {
} }
public void enabledDisplayContactOrganization(boolean enabled) { public void enabledDisplayContactOrganization(boolean enabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "display_contact_organization", enabled); getConfig().setBool("app", "display_contact_organization", enabled);
} }
// End of contact settings // End of contact settings
// Call settings // Call settings
public boolean acceptMediaEncryptionMandatory() { public boolean isMediaEncryptionMandatory() {
if (getLc() == null) return false;
return getLc().isMediaEncryptionMandatory(); return getLc().isMediaEncryptionMandatory();
} }
public void setMediaEncryptionMandatory(boolean accept) { public void setMediaEncryptionMandatory(boolean accept) {
if (getLc() == null) return;
getLc().setMediaEncryptionMandatory(accept); getLc().setMediaEncryptionMandatory(accept);
} }
public boolean acceptIncomingEarlyMedia() { public boolean acceptIncomingEarlyMedia() {
if (getConfig() == null) return false;
return getConfig().getBool("sip", "incoming_calls_early_media", false); return getConfig().getBool("sip", "incoming_calls_early_media", false);
} }
public void setAcceptIncomingEarlyMedia(boolean accept) { public void setAcceptIncomingEarlyMedia(boolean accept) {
if (getConfig() == null) return;
getConfig().setBool("sip", "incoming_calls_early_media", accept); getConfig().setBool("sip", "incoming_calls_early_media", accept);
} }
@ -506,28 +518,34 @@ public class LinphonePreferences {
} }
public String getVoiceMailUri() { public String getVoiceMailUri() {
if (getConfig() == null) return null;
return getConfig().getString("app", "voice_mail", null); return getConfig().getString("app", "voice_mail", null);
} }
public void setVoiceMailUri(String uri) { public void setVoiceMailUri(String uri) {
if (getConfig() == null) return;
getConfig().setString("app", "voice_mail", uri); getConfig().setString("app", "voice_mail", uri);
} }
public boolean getNativeDialerCall() { public boolean getNativeDialerCall() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "native_dialer_call", false); return getConfig().getBool("app", "native_dialer_call", false);
} }
public void setNativeDialerCall(boolean use) { public void setNativeDialerCall(boolean use) {
if (getConfig() == null) return;
getConfig().setBool("app", "native_dialer_call", use); getConfig().setBool("app", "native_dialer_call", use);
} }
// End of call settings // End of call settings
public boolean isWifiOnlyEnabled() { public boolean isWifiOnlyEnabled() {
if (getLc() == null) return false;
return getLc().wifiOnlyEnabled(); return getLc().wifiOnlyEnabled();
} }
// Network settings // Network settings
public void setWifiOnlyEnabled(Boolean enable) { public void setWifiOnlyEnabled(Boolean enable) {
if (getLc() == null) return;
getLc().enableWifiOnly(enable); getLc().enableWifiOnly(enable);
} }
@ -536,6 +554,7 @@ public class LinphonePreferences {
} }
private void useRandomPort(boolean enabled, boolean apply) { private void useRandomPort(boolean enabled, boolean apply) {
if (getConfig() == null) return;
getConfig().setBool("app", "random_port", enabled); getConfig().setBool("app", "random_port", enabled);
if (apply) { if (apply) {
if (enabled) { if (enabled) {
@ -547,6 +566,7 @@ public class LinphonePreferences {
} }
public boolean isUsingRandomPort() { public boolean isUsingRandomPort() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "random_port", true); return getConfig().getBool("app", "random_port", true);
} }
@ -677,10 +697,12 @@ public class LinphonePreferences {
} }
public boolean isPushNotificationEnabled() { public boolean isPushNotificationEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "push_notification", true); return getConfig().getBool("app", "push_notification", true);
} }
public void setPushNotificationEnabled(boolean enable) { public void setPushNotificationEnabled(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "push_notification", enable); getConfig().setBool("app", "push_notification", enable);
Core core = getLc(); Core core = getLc();
@ -748,6 +770,7 @@ public class LinphonePreferences {
} }
private String getPushNotificationRegistrationID() { private String getPushNotificationRegistrationID() {
if (getConfig() == null) return null;
return getConfig().getString("app", "push_notification_regid", null); return getConfig().getString("app", "push_notification_regid", null);
} }
@ -770,30 +793,36 @@ public class LinphonePreferences {
// End of network settings // End of network settings
public boolean isDebugEnabled() { public boolean isDebugEnabled() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "debug", false); return getConfig().getBool("app", "debug", false);
} }
// Advanced settings // Advanced settings
public void setDebugEnabled(boolean enabled) { public void setDebugEnabled(boolean enabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "debug", enabled); getConfig().setBool("app", "debug", enabled);
LinphoneUtils.configureLoggingService(enabled, mContext.getString(R.string.app_name)); LinphoneUtils.configureLoggingService(enabled, mContext.getString(R.string.app_name));
} }
public void setJavaLogger(boolean enabled) { public void setJavaLogger(boolean enabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "java_logger", enabled); getConfig().setBool("app", "java_logger", enabled);
LinphoneUtils.configureLoggingService( LinphoneUtils.configureLoggingService(
isDebugEnabled(), mContext.getString(R.string.app_name)); isDebugEnabled(), mContext.getString(R.string.app_name));
} }
public boolean useJavaLogger() { public boolean useJavaLogger() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "java_logger", false); return getConfig().getBool("app", "java_logger", false);
} }
public boolean isAutoStartEnabled() { public boolean isAutoStartEnabled() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "auto_start", false); return getConfig().getBool("app", "auto_start", false);
} }
public void setAutoStart(boolean autoStartEnabled) { public void setAutoStart(boolean autoStartEnabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "auto_start", autoStartEnabled); getConfig().setBool("app", "auto_start", autoStartEnabled);
} }
@ -975,80 +1004,99 @@ public class LinphonePreferences {
} }
public int getCodecBitrateLimit() { public int getCodecBitrateLimit() {
if (getConfig() == null) return 36;
return getConfig().getInt("audio", "codec_bitrate_limit", 36); return getConfig().getInt("audio", "codec_bitrate_limit", 36);
} }
public void setCodecBitrateLimit(int bitrate) { public void setCodecBitrateLimit(int bitrate) {
if (getConfig() == null) return;
getConfig().setInt("audio", "codec_bitrate_limit", bitrate); getConfig().setInt("audio", "codec_bitrate_limit", bitrate);
} }
public String getXmlrpcUrl() { public String getXmlrpcUrl() {
if (getConfig() == null) return null;
return getConfig().getString("assistant", "xmlrpc_url", null); return getConfig().getString("assistant", "xmlrpc_url", null);
} }
public String getLinkPopupTime() { public String getLinkPopupTime() {
if (getConfig() == null) return null;
return getConfig().getString("app", "link_popup_time", null); return getConfig().getString("app", "link_popup_time", null);
} }
public void setLinkPopupTime(String date) { public void setLinkPopupTime(String date) {
if (getConfig() == null) return;
getConfig().setString("app", "link_popup_time", date); getConfig().setString("app", "link_popup_time", date);
} }
public boolean isLinkPopupEnabled() { public boolean isLinkPopupEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "link_popup_enabled", true); return getConfig().getBool("app", "link_popup_enabled", true);
} }
public void enableLinkPopup(boolean enable) { public void enableLinkPopup(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "link_popup_enabled", enable); getConfig().setBool("app", "link_popup_enabled", enable);
} }
public boolean isDNDSettingsPopupEnabled() { public boolean isDNDSettingsPopupEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "dnd_settings_popup_enabled", true); return getConfig().getBool("app", "dnd_settings_popup_enabled", true);
} }
public void enableDNDSettingsPopup(boolean enable) { public void enableDNDSettingsPopup(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "dnd_settings_popup_enabled", enable); getConfig().setBool("app", "dnd_settings_popup_enabled", enable);
} }
public boolean isLimeSecurityPopupEnabled() { public boolean isLimeSecurityPopupEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "lime_security_popup_enabled", true); return getConfig().getBool("app", "lime_security_popup_enabled", true);
} }
public void enableLimeSecurityPopup(boolean enable) { public void enableLimeSecurityPopup(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "lime_security_popup_enabled", enable); getConfig().setBool("app", "lime_security_popup_enabled", enable);
} }
public String getDebugPopupAddress() { public String getDebugPopupAddress() {
if (getConfig() == null) return null;
return getConfig().getString("app", "debug_popup_magic", null); return getConfig().getString("app", "debug_popup_magic", null);
} }
public String getActivityToLaunchOnIncomingReceived() { public String getActivityToLaunchOnIncomingReceived() {
if (getConfig() == null) return "org.linphone.call.CallIncomingActivity";
return getConfig() return getConfig()
.getString( .getString(
"app", "incoming_call_activity", "org.linphone.call.CallIncomingActivity"); "app", "incoming_call_activity", "org.linphone.call.CallIncomingActivity");
} }
public void setActivityToLaunchOnIncomingReceived(String name) { public void setActivityToLaunchOnIncomingReceived(String name) {
if (getConfig() == null) return;
getConfig().setString("app", "incoming_call_activity", name); getConfig().setString("app", "incoming_call_activity", name);
} }
public boolean getServiceNotificationVisibility() { public boolean getServiceNotificationVisibility() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "show_service_notification", false); return getConfig().getBool("app", "show_service_notification", false);
} }
public void setServiceNotificationVisibility(boolean enable) { public void setServiceNotificationVisibility(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "show_service_notification", enable); getConfig().setBool("app", "show_service_notification", enable);
} }
public String getCheckReleaseUrl() { public String getCheckReleaseUrl() {
if (getConfig() == null) return null;
return getConfig().getString("misc", "version_check_url_root", null); return getConfig().getString("misc", "version_check_url_root", null);
} }
public int getLastCheckReleaseTimestamp() { public int getLastCheckReleaseTimestamp() {
if (getConfig() == null) return 0;
return getConfig().getInt("app", "version_check_url_last_timestamp", 0); return getConfig().getInt("app", "version_check_url_last_timestamp", 0);
} }
public void setLastCheckReleaseTimestamp(int timestamp) { public void setLastCheckReleaseTimestamp(int timestamp) {
if (getConfig() == null) return;
getConfig().setInt("app", "version_check_url_last_timestamp", timestamp); getConfig().setInt("app", "version_check_url_last_timestamp", timestamp);
} }
@ -1058,10 +1106,12 @@ public class LinphonePreferences {
// Disable overlay and use PIP feature // Disable overlay and use PIP feature
return false; return false;
} }
if (getConfig() == null) return false;
return getConfig().getBool("app", "display_overlay", false); return getConfig().getBool("app", "display_overlay", false);
} }
public void enableOverlay(boolean enable) { public void enableOverlay(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "display_overlay", enable); getConfig().setBool("app", "display_overlay", enable);
} }
@ -1071,71 +1121,87 @@ public class LinphonePreferences {
.checkPermission( .checkPermission(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
mContext.getPackageName()); mContext.getPackageName());
if (getConfig() == null) return readExternalStorage == PackageManager.PERMISSION_GRANTED;
return getConfig().getBool("app", "device_ringtone", true) return getConfig().getBool("app", "device_ringtone", true)
&& readExternalStorage == PackageManager.PERMISSION_GRANTED; && readExternalStorage == PackageManager.PERMISSION_GRANTED;
} }
public void enableDeviceRingtone(boolean enable) { public void enableDeviceRingtone(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "device_ringtone", enable); getConfig().setBool("app", "device_ringtone", enable);
LinphoneManager.getInstance().enableDeviceRingtone(enable); LinphoneManager.getInstance().enableDeviceRingtone(enable);
} }
public boolean isIncomingCallVibrationEnabled() { public boolean isIncomingCallVibrationEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "incoming_call_vibration", true); return getConfig().getBool("app", "incoming_call_vibration", true);
} }
public void enableIncomingCallVibration(boolean enable) { public void enableIncomingCallVibration(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "incoming_call_vibration", enable); getConfig().setBool("app", "incoming_call_vibration", enable);
} }
public boolean isBisFeatureEnabled() { public boolean isBisFeatureEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "bis_feature", true); return getConfig().getBool("app", "bis_feature", true);
} }
public boolean isAutoAnswerEnabled() { public boolean isAutoAnswerEnabled() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "auto_answer", false); return getConfig().getBool("app", "auto_answer", false);
} }
public void enableAutoAnswer(boolean enable) { public void enableAutoAnswer(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "auto_answer", enable); getConfig().setBool("app", "auto_answer", enable);
} }
public int getAutoAnswerTime() { public int getAutoAnswerTime() {
if (getConfig() == null) return 0;
return getConfig().getInt("app", "auto_answer_delay", 0); return getConfig().getInt("app", "auto_answer_delay", 0);
} }
public void setAutoAnswerTime(int time) { public void setAutoAnswerTime(int time) {
if (getConfig() == null) return;
getConfig().setInt("app", "auto_answer_delay", time); getConfig().setInt("app", "auto_answer_delay", time);
} }
public void disableFriendsStorage() { public void disableFriendsStorage() {
if (getConfig() == null) return;
getConfig().setBool("misc", "store_friends", false); getConfig().setBool("misc", "store_friends", false);
} }
public boolean useBasicChatRoomFor1To1() { public boolean useBasicChatRoomFor1To1() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "prefer_basic_chat_room", false); return getConfig().getBool("app", "prefer_basic_chat_room", false);
} }
// 0 is download all, -1 is disable feature, else size is bytes // 0 is download all, -1 is disable feature, else size is bytes
public int getAutoDownloadFileMaxSize() { public int getAutoDownloadFileMaxSize() {
if (getLc() == null) return -1;
return getLc().getMaxSizeForAutoDownloadIncomingFiles(); return getLc().getMaxSizeForAutoDownloadIncomingFiles();
} }
// 0 is download all, -1 is disable feature, else size is bytes // 0 is download all, -1 is disable feature, else size is bytes
public void setAutoDownloadFileMaxSize(int size) { public void setAutoDownloadFileMaxSize(int size) {
if (getLc() == null) return;
getLc().setMaxSizeForAutoDownloadIncomingFiles(size); getLc().setMaxSizeForAutoDownloadIncomingFiles(size);
} }
public boolean hasPowerSaverDialogBeenPrompted() { public boolean hasPowerSaverDialogBeenPrompted() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "android_power_saver_dialog", false); return getConfig().getBool("app", "android_power_saver_dialog", false);
} }
public void powerSaverDialogPrompted(boolean b) { public void powerSaverDialogPrompted(boolean b) {
if (getConfig() == null) return;
getConfig().setBool("app", "android_power_saver_dialog", b); getConfig().setBool("app", "android_power_saver_dialog", b);
} }
public boolean isDarkModeEnabled() { public boolean isDarkModeEnabled() {
if (getConfig() == null) return false; if (getConfig() == null) return false;
if (!mContext.getResources().getBoolean(R.bool.allow_dark_mode)) return false;
boolean useNightModeDefault = boolean useNightModeDefault =
AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES; AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES;
@ -1152,63 +1218,78 @@ public class LinphonePreferences {
} }
public void enableDarkMode(boolean enable) { public void enableDarkMode(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "dark_mode", enable); getConfig().setBool("app", "dark_mode", enable);
} }
public String getDeviceName(Context context) { public String getDeviceName(Context context) {
String defaultValue = Compatibility.getDeviceName(context); String defaultValue = Compatibility.getDeviceName(context);
if (getConfig() == null) return defaultValue;
return getConfig().getString("app", "device_name", defaultValue); return getConfig().getString("app", "device_name", defaultValue);
} }
public void setDeviceName(String name) { public void setDeviceName(String name) {
if (getConfig() == null) return;
getConfig().setString("app", "device_name", name); getConfig().setString("app", "device_name", name);
} }
public boolean isEchoCancellationCalibrationDone() { public boolean isEchoCancellationCalibrationDone() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "echo_cancellation_calibration_done", false); return getConfig().getBool("app", "echo_cancellation_calibration_done", false);
} }
public void setEchoCancellationCalibrationDone(boolean done) { public void setEchoCancellationCalibrationDone(boolean done) {
if (getConfig() == null) return;
getConfig().setBool("app", "echo_cancellation_calibration_done", done); getConfig().setBool("app", "echo_cancellation_calibration_done", done);
} }
public boolean isOpenH264CodecDownloadEnabled() { public boolean isOpenH264CodecDownloadEnabled() {
if (getConfig() == null) return true;
return getConfig().getBool("app", "open_h264_download_enabled", true); return getConfig().getBool("app", "open_h264_download_enabled", true);
} }
public void setOpenH264CodecDownloadEnabled(boolean enable) { public void setOpenH264CodecDownloadEnabled(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "open_h264_download_enabled", enable); getConfig().setBool("app", "open_h264_download_enabled", enable);
} }
public boolean isVideoPreviewEnabled() { public boolean isVideoPreviewEnabled() {
if (getConfig() == null) return false;
return isVideoEnabled() && getConfig().getBool("app", "video_preview", false); return isVideoEnabled() && getConfig().getBool("app", "video_preview", false);
} }
public void setVideoPreviewEnabled(boolean enabled) { public void setVideoPreviewEnabled(boolean enabled) {
if (getConfig() == null) return;
getConfig().setBool("app", "video_preview", enabled); getConfig().setBool("app", "video_preview", enabled);
} }
public boolean shortcutsCreationEnabled() { public boolean shortcutsCreationEnabled() {
if (getConfig() == null) return false;
return getConfig().getBool("app", "shortcuts", false); return getConfig().getBool("app", "shortcuts", false);
} }
public void enableChatRoomsShortcuts(boolean enable) { public void enableChatRoomsShortcuts(boolean enable) {
if (getConfig() == null) return;
getConfig().setBool("app", "shortcuts", enable); getConfig().setBool("app", "shortcuts", enable);
} }
public boolean hideEmptyChatRooms() { public boolean hideEmptyChatRooms() {
if (getConfig() == null) return true;
return getConfig().getBool("misc", "hide_empty_chat_rooms", true); return getConfig().getBool("misc", "hide_empty_chat_rooms", true);
} }
public void setHideEmptyChatRooms(boolean hide) { public void setHideEmptyChatRooms(boolean hide) {
if (getConfig() == null) return;
getConfig().setBool("misc", "hide_empty_chat_rooms", hide); getConfig().setBool("misc", "hide_empty_chat_rooms", hide);
} }
public boolean hideRemovedProxiesChatRooms() { public boolean hideRemovedProxiesChatRooms() {
if (getConfig() == null) return true;
return getConfig().getBool("misc", "hide_chat_rooms_from_removed_proxies", true); return getConfig().getBool("misc", "hide_chat_rooms_from_removed_proxies", true);
} }
public void setHideRemovedProxiesChatRooms(boolean hide) { public void setHideRemovedProxiesChatRooms(boolean hide) {
if (getConfig() == null) return;
getConfig().setBool("misc", "hide_chat_rooms_from_removed_proxies", hide); getConfig().setBool("misc", "hide_chat_rooms_from_removed_proxies", hide);
} }
} }

View file

@ -312,6 +312,7 @@ public class VideoSettingsFragment extends SettingsFragment {
: View.GONE); : View.GONE);
mAutoInitiate.setVisibility(show ? View.VISIBLE : View.GONE); mAutoInitiate.setVisibility(show ? View.VISIBLE : View.GONE);
mAutoAccept.setVisibility(show ? View.VISIBLE : View.GONE); mAutoAccept.setVisibility(show ? View.VISIBLE : View.GONE);
mCameraDevices.setVisibility(show ? View.VISIBLE : View.GONE);
mOverlay.setVisibility(show ? View.VISIBLE : View.GONE); mOverlay.setVisibility(show ? View.VISIBLE : View.GONE);
mBandwidth.setVisibility(show ? View.VISIBLE : View.GONE); mBandwidth.setVisibility(show ? View.VISIBLE : View.GONE);
mPreset.setVisibility(show ? View.VISIBLE : View.GONE); mPreset.setVisibility(show ? View.VISIBLE : View.GONE);
@ -319,6 +320,17 @@ public class VideoSettingsFragment extends SettingsFragment {
mFps.setVisibility(show ? View.VISIBLE : View.GONE); mFps.setVisibility(show ? View.VISIBLE : View.GONE);
mVideoCodecs.setVisibility(show ? View.VISIBLE : View.GONE); mVideoCodecs.setVisibility(show ? View.VISIBLE : View.GONE);
mVideoCodecsHeader.setVisibility(show ? View.VISIBLE : View.GONE); mVideoCodecsHeader.setVisibility(show ? View.VISIBLE : View.GONE);
if (show) {
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.setVisibility(
mPrefs.getVideoPreset().equals("custom") ? View.VISIBLE : View.GONE);
mFps.setVisibility(mPrefs.getVideoPreset().equals("custom") ? View.VISIBLE : View.GONE);
}
} }
private void initCameraDevicesList() { private void initCameraDevicesList() {

View file

@ -151,9 +151,10 @@ public class DeviceUtils {
for (final Intent intent : POWERMANAGER_INTENTS) { for (final Intent intent : POWERMANAGER_INTENTS) {
if (DeviceUtils.isIntentCallable(context, intent)) { if (DeviceUtils.isIntentCallable(context, intent)) {
Log.w( Log.w(
"[Hacks] " "[Hacks] ",
+ android.os.Build.MANUFACTURER android.os.Build.MANUFACTURER,
+ " device with power saver detected !"); " device with power saver detected: ",
intent.getComponent().getClassName());
if (!LinphonePreferences.instance().hasPowerSaverDialogBeenPrompted()) { if (!LinphonePreferences.instance().hasPowerSaverDialogBeenPrompted()) {
Log.w("[Hacks] Asking power saver for whitelist !"); Log.w("[Hacks] Asking power saver for whitelist !");
@ -197,12 +198,19 @@ public class DeviceUtils {
public void onClick(View v) { public void onClick(View v) {
Log.w( Log.w(
"[Hacks] Power saver detected, user is going to settings :)"); "[Hacks] Power saver detected, user is going to settings :)");
if (doNotAskAgain.isChecked()) { // If user is going into the settings,
LinphonePreferences.instance() // assume it will make the change so don't prompt again
.powerSaverDialogPrompted(true); LinphonePreferences.instance().powerSaverDialogPrompted(true);
}
try {
context.startActivity(intent); context.startActivity(intent);
} catch (SecurityException se) {
Log.e(
"[Hacks] Couldn't start intent [",
intent.getComponent().getClassName(),
"], security exception was thrown: ",
se);
}
dialog.dismiss(); dialog.dismiss();
} }
}); });

View file

@ -22,6 +22,7 @@ package org.linphone.utils;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuffXfermode;
@ -55,7 +56,7 @@ public class ImageUtils {
return bm; return bm;
} }
private static Bitmap getRoundBitmap(Bitmap bitmap) { public static Bitmap getRoundBitmap(Bitmap bitmap) {
Bitmap output = Bitmap output =
Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output); Canvas canvas = new Canvas(output);
@ -85,4 +86,14 @@ public class ImageUtils {
/ ((float) context.getResources().getDisplayMetrics().densityDpi / ((float) context.getResources().getDisplayMetrics().densityDpi
/ DisplayMetrics.DENSITY_DEFAULT); / DisplayMetrics.DENSITY_DEFAULT);
} }
public static Bitmap rotateImage(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
Bitmap rotatedBitmap =
Bitmap.createBitmap(
source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
source.recycle();
return rotatedBitmap;
}
} }

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.views;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import java.lang.ref.WeakReference;
class AsyncBitmap extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> mBitmapWorkerTaskReference;
public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
mBitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return mBitmapWorkerTaskReference.get();
}
}

View file

@ -1,166 +0,0 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import java.io.IOException;
import java.lang.ref.WeakReference;
import org.linphone.LinphoneContext;
import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils;
public class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private String path;
private final WeakReference<ImageView> mImageViewReference;
private final Bitmap mDefaultBitmap;
private final int mImageViewHeight;
public BitmapWorkerTask(ImageView imageView, Bitmap defaultBitmap) {
mDefaultBitmap = defaultBitmap;
path = null;
// Use a WeakReference to ensure the ImageView can be garbage collected
mImageViewReference = new WeakReference<>(imageView);
mImageViewHeight = imageView.getMeasuredHeight();
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncBitmap) {
final AsyncBitmap asyncDrawable = (AsyncBitmap) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
private Bitmap scaleToFitHeight(Bitmap b, int height) {
float factor = height / (float) b.getHeight();
int dstWidth = (int) (b.getWidth() * factor);
if (dstWidth > 0 && height > 0) {
return Bitmap.createScaledBitmap(b, dstWidth, height, true);
}
return b;
}
// Decode image in background.
@Override
protected Bitmap doInBackground(String... params) {
Context context = LinphoneContext.instance().getApplicationContext();
path = params[0];
Bitmap bm = null;
Bitmap thumbnail = null;
if (FileUtils.isExtensionImage(path)) {
if (path.startsWith("content")) {
try {
bm =
MediaStore.Images.Media.getBitmap(
context.getContentResolver(), Uri.parse(path));
} catch (IOException e) {
Log.e(e);
}
} else {
bm = BitmapFactory.decodeFile(path);
}
ImageView imageView = mImageViewReference.get();
try {
// Rotate the bitmap if possible/needed, using EXIF data
Matrix matrix = new Matrix();
ExifInterface exif = new ExifInterface(path);
int width = bm.getWidth();
int height = bm.getHeight();
int pictureOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
if (pictureOrientation == 6 || pictureOrientation == 3 || pictureOrientation == 8) {
if (imageView != null) {
float factor = (float) mImageViewHeight / height;
matrix.postScale(factor, factor);
}
if (pictureOrientation == 6) {
matrix.preRotate(90);
} else if (pictureOrientation == 3) {
matrix.preRotate(180);
} else {
matrix.preRotate(270);
}
thumbnail = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
if (thumbnail != bm) {
bm.recycle();
bm = null;
}
}
} catch (Exception e) {
Log.e(e);
}
if (thumbnail == null && bm != null) {
if (imageView == null) return bm;
thumbnail = scaleToFitHeight(bm, mImageViewHeight);
if (thumbnail != bm) {
bm.recycle();
}
}
return thumbnail;
} else {
return mDefaultBitmap;
}
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap.recycle();
bitmap = null;
}
if (mImageViewReference != null && bitmap != null) {
Context context = LinphoneContext.instance().getApplicationContext();
final ImageView imageView = mImageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
if (bitmap.getWidth() > ImageUtils.dpToPixels(context, 300)) {
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(
bitmap.getWidth(), ViewGroup.LayoutParams.WRAP_CONTENT);
int margin = (int) ImageUtils.dpToPixels(context, 5);
params.setMargins(margin, margin, margin, margin);
imageView.setLayoutParams(params);
imageView.invalidate();
}
}
}
}
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<bitmap android:src="@drawable/dialer_alt_back" />
</item>
<item>
<bitmap android:src="@drawable/footer_dialer" />
</item>
</selector>

View file

@ -109,6 +109,7 @@
android:id="@+id/buttons" android:id="@+id/buttons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:gravity="bottom"> android:gravity="bottom">
<!-- This is a better way of splitting screen 50/50 than using weights --> <!-- This is a better way of splitting screen 50/50 than using weights -->
@ -119,29 +120,30 @@
android:layout_centerHorizontal="true" /> android:layout_centerHorizontal="true" />
<include layout="@layout/call_primary_buttons" <include layout="@layout/call_primary_buttons"
android:id="@+id/call_primary_buttons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_toLeftOf="@id/vertical_divider" /> android:layout_toLeftOf="@id/vertical_divider" />
<ViewStub
android:id="@+id/numpad"
android:inflatedId="@+id/numpad"
android:layout="@layout/numpad"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_above="@id/call_primary_buttons"
android:background="@color/toolbar_color" />
<include layout="@layout/call_secondary_buttons" <include layout="@layout/call_secondary_buttons"
android:id="@+id/call_secondary_buttons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_toRightOf="@id/vertical_divider" /> android:layout_toRightOf="@id/vertical_divider" />
</RelativeLayout> </RelativeLayout>
<ViewStub
android:id="@+id/numpad"
android:inflatedId="@+id/numpad"
android:layout="@layout/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" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout

View file

@ -11,7 +11,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:background="?attr/lighToolbarBackgroundColor"> android:background="?attr/lighToolbarBackgroundColor">
<org.linphone.views.EraseButton <org.linphone.dialer.views.EraseButton
android:id="@+id/erase" android:id="@+id/erase"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -23,7 +23,7 @@
android:contentDescription="@string/content_description_backspace" android:contentDescription="@string/content_description_backspace"
android:src="@drawable/backspace" /> android:src="@drawable/backspace" />
<org.linphone.views.AddressText <org.linphone.dialer.views.AddressText
android:id="@+id/address" android:id="@+id/address"
style="@style/numpad_composed_number_font" style="@style/numpad_composed_number_font"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -81,7 +81,7 @@
</RelativeLayout> </RelativeLayout>
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/start_call" android:id="@+id/start_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -91,7 +91,7 @@
android:padding="12dp" android:padding="12dp"
android:src="@drawable/call_audio_start" /> android:src="@drawable/call_audio_start" />
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/add_call" android:id="@+id/add_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -102,7 +102,7 @@
android:visibility="gone" android:visibility="gone"
android:src="@drawable/call_add" /> android:src="@drawable/call_add" />
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/transfer_call" android:id="@+id/transfer_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -42,7 +42,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:background="?attr/lighToolbarBackgroundColor"> android:background="?attr/lighToolbarBackgroundColor">
<org.linphone.views.EraseButton <org.linphone.dialer.views.EraseButton
android:id="@+id/erase" android:id="@+id/erase"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -54,7 +54,7 @@
android:contentDescription="@string/content_description_backspace" android:contentDescription="@string/content_description_backspace"
android:src="@drawable/backspace" /> android:src="@drawable/backspace" />
<org.linphone.views.AddressText <org.linphone.dialer.views.AddressText
android:id="@+id/address" android:id="@+id/address"
style="@style/numpad_composed_number_font" style="@style/numpad_composed_number_font"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -115,7 +115,7 @@
</RelativeLayout> </RelativeLayout>
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/start_call" android:id="@+id/start_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -125,7 +125,7 @@
android:padding="12dp" android:padding="12dp"
android:src="@drawable/call_audio_start" /> android:src="@drawable/call_audio_start" />
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/add_call" android:id="@+id/add_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -136,7 +136,7 @@
android:visibility="gone" android:visibility="gone"
android:src="@drawable/call_add" /> android:src="@drawable/call_add" />
<org.linphone.views.CallButton <org.linphone.call.views.CallButton
android:id="@+id/transfer_call" android:id="@+id/transfer_call"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

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