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
source_file = app/src/main/res/values/strings.xml
source_lang = en

View file

@ -12,6 +12,14 @@ Group changes to describe their impact on the project, as follows:
## [Unreleased]
### Added
-
### Changed
-
## [4.2.0] - 2019-12-09
### Added
- Added shortcuts to contacts' latest chat rooms
- 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)
- Android 10 compatibility
- 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
- Call statistics are now available for each call & conference
- 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)
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
@ -98,7 +126,7 @@ to push new strings to transifex so they can be translated.
In order to submit a patch for inclusion in linphone's source code:
1. First make sure your patch applies to latest git sources before submitting: patches made to old versions can't and won't be merged.
2. Fill out and send us an email with the link of pullrequest and the [Contributor Agreement](http://www.belledonne-communications.com/downloads/Belledonne_communications_CA.pdf) for your patch to be included in the git tree.
1. First make sure your patch applies to latest git sources before submitting: patches made to old versions can't and won't be merged.
2. Fill out and send us an email with the link of pullrequest and the [Contributor Agreement](http://www.belledonne-communications.com/downloads/Belledonne_communications_CA.pdf) for your patch to be included in the git tree.
The goal of this agreement to grant us peaceful exercise of our rights on the linphone source code, while not losing your rights on your contribution.

View file

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

View file

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

View file

@ -21,28 +21,33 @@ package org.linphone;
import static android.content.Intent.ACTION_MAIN;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.ContactsContract;
import java.util.ArrayList;
import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity;
import org.linphone.call.CallOutgoingActivity;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager;
import org.linphone.core.Call;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
import org.linphone.core.LoggingServiceListener;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
import org.linphone.notifications.NotificationsManager;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.DeviceUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.PushNotificationUtils;
public class LinphoneContext {
private static LinphoneContext sInstance = null;
@ -78,7 +83,7 @@ public class LinphoneContext {
private NotificationsManager mNotificationManager;
private LinphoneManager mLinphoneManager;
private ContactsManager mContactsManager;
private Class<? extends Activity> mIncomingReceivedActivity = CallIncomingActivity.class;
private final ArrayList<CoreStartedListener> mCoreStartedListeners;
public static boolean isReady() {
return sInstance != null;
@ -90,6 +95,7 @@ public class LinphoneContext {
public LinphoneContext(Context context) {
mContext = context;
mCoreStartedListeners = new ArrayList<>();
LinphonePreferences.instance().setContext(context);
Factory.instance().setLogCollectionPath(context.getFilesDir().getAbsolutePath());
@ -100,23 +106,40 @@ public class LinphoneContext {
dumpDeviceInformation();
dumpLinphoneInformation();
String incomingReceivedActivityName =
LinphonePreferences.instance().getActivityToLaunchOnIncomingReceived();
try {
mIncomingReceivedActivity =
(Class<? extends Activity>) Class.forName(incomingReceivedActivityName);
} catch (ClassNotFoundException e) {
Log.e(e);
}
sInstance = this;
Log.i("[Context] Ready");
mListener =
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
public void onCallStateChanged(
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)) {
mNotificationManager.displayCallNotification(call);
}
@ -157,12 +180,28 @@ public class LinphoneContext {
mLinphoneManager = new LinphoneManager(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) {
Log.i("[Context] Starting");
mLinphoneManager.startLibLinphone(isPush);
LinphoneManager.getCore().addListener(mListener);
Log.i("[Context] Starting, push status is ", isPush);
mLinphoneManager.startLibLinphone(isPush, mListener);
mNotificationManager.onCoreReady();
@ -236,6 +275,14 @@ public class LinphoneContext {
return mContactsManager;
}
public void addCoreStartedListener(CoreStartedListener listener) {
mCoreStartedListeners.add(listener);
}
public void removeCoreStartedListener(CoreStartedListener listener) {
mCoreStartedListeners.remove(listener);
}
/* Log device related information */
private void dumpDeviceInformation() {
@ -266,7 +313,7 @@ public class LinphoneContext {
/* Call activities */
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
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
@ -285,4 +332,8 @@ public class LinphoneContext {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
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.TimerTask;
import org.linphone.assistant.PhoneAccountLinkingAssistantActivity;
import org.linphone.call.AndroidAudioManager;
import org.linphone.call.CallManager;
import org.linphone.contacts.ContactsManager;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Call;
import org.linphone.core.Call.State;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core;
import org.linphone.core.CoreListener;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.FriendList;
import org.linphone.core.GlobalState;
import org.linphone.core.PresenceActivity;
import org.linphone.core.PresenceBasicStatus;
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.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.AndroidAudioManager;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.MediaScanner;
import org.linphone.utils.PushNotificationUtils;
@ -160,37 +159,6 @@ public class LinphoneManager implements SensorEventListener {
mCoreListener =
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")
@Override
public void onCallStateChanged(
@ -198,7 +166,7 @@ public class LinphoneManager implements SensorEventListener {
final Call call,
final State state,
final String message) {
Log.i("[Manager] New call state [", state, "]");
Log.i("[Manager] Call state is [", state, "]");
if (state == State.IncomingReceived
&& !call.equals(core.getCurrentCall())) {
if (call.getReplacedCall() != null) {
@ -388,6 +356,7 @@ public class LinphoneManager implements SensorEventListener {
}
public void restartCore() {
Log.w("[Manager] Restarting Core");
mCore.stop();
mCore.start();
}
@ -395,8 +364,8 @@ public class LinphoneManager implements SensorEventListener {
private void destroyCore() {
Log.w("[Manager] Destroying Core");
if (LinphonePreferences.instance() != null) {
// We set network reachable at false before destroy LC to not send register with expires
// at 0
// We set network reachable at false before destroying the Core
// to not send a register with expires at 0
if (LinphonePreferences.instance().isPushNotificationEnabled()) {
Log.w(
"[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 {
mCore =
Factory.instance()
@ -437,6 +406,7 @@ public class LinphoneManager implements SensorEventListener {
mPrefs.getLinphoneDefaultConfig(),
mPrefs.getLinphoneFactoryConfig(),
mContext);
mCore.addListener(listener);
mCore.addListener(mCoreListener);
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*/
mTimer = new Timer("Linphone scheduler");
mTimer.schedule(lTask, 0, 20);
configureCore();
} catch (Exception e) {
Log.e(e, "[Manager] Cannot start linphone");
}
@ -474,8 +446,8 @@ public class LinphoneManager implements SensorEventListener {
H264Helper.setH264Mode(H264Helper.MODE_AUTO, mCore);
}
private synchronized void initLiblinphone(Core core) {
mCore = core;
private synchronized void configureCore() {
Log.i("[Manager] Configuring Core");
mAudioManager = new AndroidAudioManager(mContext);
mCore.setZrtpSecretsFile(mBasePath + "/zrtp_secrets");
@ -544,6 +516,8 @@ public class LinphoneManager implements SensorEventListener {
mAccountCreator = mCore.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(mAccountCreatorListener);
mCallGsmON = false;
Log.i("[Manager] Core configured");
}
public void resetCameraFromPreferences() {
@ -572,16 +546,28 @@ public class LinphoneManager implements SensorEventListener {
/* 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() {
if (mCore.getDefaultProxyConfig() != null) {
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) {
mAccountCreator.setUsername(
accountCreator.reset();
accountCreator.setUsername(
LinphonePreferences.instance()
.getAccountUsername(
LinphonePreferences.instance().getDefaultAccountIndex()));
mAccountCreator.isAccountExist();
accountCreator.isAccountExist();
}
} else {
LinphonePreferences.instance().setLinkPopupTime(null);
@ -839,7 +825,7 @@ public class LinphoneManager implements SensorEventListener {
public void setCallGsmON(boolean on) {
mCallGsmON = on;
if (on) {
if (on && mCore != null) {
mCore.pauseAllCalls();
}
}

View file

@ -24,9 +24,9 @@ import android.os.Bundle;
import android.view.Surface;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.core.Core;
import org.linphone.core.tools.Log;
import org.linphone.service.LinphoneService;
public abstract class LinphoneGenericActivity extends ThemeableActivity {
@Override
@ -80,8 +80,15 @@ public abstract class LinphoneGenericActivity extends ThemeableActivity {
if (!LinphoneContext.isReady()) {
new LinphoneContext(getApplicationContext());
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));
} catch (IllegalStateException ise) {
Log.e("[Generic Activity] Couldn't start service, exception: ", ise);
}
startService(new Intent().setClass(this, LinphoneService.class));
}
}
}

View file

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

View file

@ -20,7 +20,6 @@
package org.linphone.activities;
import android.Manifest;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
@ -46,7 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import java.util.ArrayList;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity;
@ -66,6 +64,7 @@ import org.linphone.core.CoreListenerStub;
import org.linphone.core.ProxyConfig;
import org.linphone.core.RegistrationState;
import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.fragments.EmptyFragment;
import org.linphone.fragments.StatusBarFragment;
import org.linphone.history.HistoryActivity;
@ -74,7 +73,6 @@ import org.linphone.settings.LinphonePreferences;
import org.linphone.settings.SettingsActivity;
import org.linphone.utils.DeviceUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.PushNotificationUtils;
public abstract class MainActivity extends LinphoneGenericActivity
implements StatusBarFragment.MenuClikedListener, SideMenuFragment.QuitClikedListener {
@ -276,23 +274,6 @@ public abstract class MainActivity extends LinphoneGenericActivity
super.onStart();
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
@ -443,10 +424,6 @@ public abstract class MainActivity extends LinphoneGenericActivity
private void quit() {
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
@ -495,6 +472,14 @@ public abstract class MainActivity extends LinphoneGenericActivity
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) {
if (!checkPermission(permission)) {
Log.i("[Permission] Requesting " + permission + " permission");

View file

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

View file

@ -21,16 +21,16 @@ package org.linphone.assistant;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import java.util.Locale;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.activities.DialerActivity;
import org.linphone.activities.LinphoneGenericActivity;
import org.linphone.core.AccountCreator;
import org.linphone.core.Core;
@ -38,29 +38,16 @@ import org.linphone.core.DialPlan;
import org.linphone.core.Factory;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.settings.LinphonePreferences;
public abstract class AssistantActivity extends LinphoneGenericActivity
implements CountryPicker.CountryPickedListener {
static AccountCreator mAccountCreator;
protected ImageView mBack;
private AlertDialog mCountryPickerDialog;
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
protected void onResume() {
super.onResume();
@ -104,24 +91,77 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (!mBack.isEnabled()) return true;
}
return super.onKeyDown(keyCode, event);
}
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(mAccountCreator.getDomain());
if (useLinphoneDefaultValues) {
core.loadConfigFromXml(LinphonePreferences.instance().getLinphoneDynamicConfigFile());
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 = mAccountCreator.createProxyConfig();
ProxyConfig proxyConfig = getAccountCreator().createProxyConfig();
if (useLinphoneDefaultValues) {
// Restore default values
core.loadConfigFromXml(LinphonePreferences.instance().getDefaultDynamicConfigFile());
} else {
// If this isn't a sip.linphone.org account, disable push notifications and enable
// service notification, otherwise incoming calls won't work (most probably)
LinphonePreferences.instance().setServiceNotificationVisibility(true);
LinphoneContext.instance().getNotificationManager().startForeground();
if (isGenericAccount) {
if (useLinphoneDefaultValues) {
// Restore default values
Log.i("[Assistant] Restoring default assistant configuration");
core.loadConfigFromXml(
LinphonePreferences.instance().getDefaultDynamicConfigFile());
} else {
// If this isn't a sip.linphone.org account, disable push notifications and enable
// 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);
LinphoneContext.instance().getNotificationManager().startForeground();
}
}
if (proxyConfig == null) {
@ -206,6 +246,9 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
case PhoneNumberOverused:
message = getString(R.string.phone_number_overuse);
break;
case AccountNotExist:
message = getString(R.string.account_doesnt_exist);
break;
default:
message = getString(R.string.error_unknown);
break;
@ -271,7 +314,7 @@ public abstract class AssistantActivity extends LinphoneGenericActivity
}
String phoneNumber = phoneNumberEditText.getText().toString();
return mAccountCreator.setPhoneNumber(phoneNumber, prefix);
return getAccountCreator().setPhoneNumber(phoneNumber, prefix);
}
String getErrorFromPhoneNumberStatus(int status) {

View file

@ -30,9 +30,11 @@ import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListenerStub;
import org.linphone.core.Core;
import org.linphone.core.tools.Log;
public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@ -65,7 +67,7 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
public void afterTextChanged(Editable s) {
AccountCreator.UsernameStatus status =
mAccountCreator.setUsername(s.toString());
getAccountCreator().setUsername(s.toString());
mUsernameError.setVisibility(
status == AccountCreator.UsernameStatus.Ok
? View.INVISIBLE
@ -88,7 +90,7 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
public void afterTextChanged(Editable s) {
AccountCreator.PasswordStatus status =
mAccountCreator.setPassword(s.toString());
getAccountCreator().setPassword(s.toString());
mPasswordError.setVisibility(
status == AccountCreator.PasswordStatus.Ok
? View.INVISIBLE
@ -146,7 +148,8 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
public void afterTextChanged(Editable s) {
AccountCreator.EmailStatus status = mAccountCreator.setEmail(s.toString());
AccountCreator.EmailStatus status =
getAccountCreator().setEmail(s.toString());
mEmailError.setVisibility(
status == AccountCreator.EmailStatus.Ok
? View.INVISIBLE
@ -161,12 +164,13 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
public void onClick(View v) {
enableButtonsAndFields(false);
mAccountCreator.setDomain(getString(R.string.default_domain));
AccountCreator.Status status = mAccountCreator.isAccountExist();
AccountCreator.Status status = getAccountCreator().isAccountExist();
if (status != AccountCreator.Status.RequestOk) {
enableButtonsAndFields(true);
Log.e("[Email Account Creation] isAccountExists returned " + status);
Log.e(
"[Email Account Creation Assistant] isAccountExists returned "
+ status);
showGenericErrorDialog(status);
}
}
@ -177,15 +181,19 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
new AccountCreatorListenerStub() {
public void onIsAccountExist(
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)
|| status.equals(AccountCreator.Status.AccountExistWithAlias)) {
showAccountAlreadyExistsDialog();
enableButtonsAndFields(true);
} else if (status.equals(AccountCreator.Status.AccountNotExist)) {
status = mAccountCreator.createAccount();
status = getAccountCreator().createAccount();
if (status != AccountCreator.Status.RequestOk) {
Log.e("[Email Account Creation] createAccount returned " + status);
Log.e(
"[Email Account Creation Assistant] createAccount returned "
+ status);
enableButtonsAndFields(true);
showGenericErrorDialog(status);
}
@ -198,7 +206,9 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
public void onCreateAccount(
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)) {
startActivity(
new Intent(
@ -235,7 +245,12 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
protected void 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)) {
Account[] accounts = AccountManager.get(this).getAccountsByType("com.google");
@ -252,6 +267,6 @@ public class EmailAccountCreationAssistantActivity extends AssistantActivity {
@Override
protected void 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);
TextView email = findViewById(R.id.send_email);
email.setText(mAccountCreator.getEmail());
email.setText(getAccountCreator().getEmail());
mFinishCreation = findViewById(R.id.assistant_check);
mFinishCreation.setOnClickListener(
@ -50,9 +50,11 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
public void onClick(View v) {
mFinishCreation.setEnabled(false);
AccountCreator.Status status = mAccountCreator.isAccountActivated();
AccountCreator.Status status = getAccountCreator().isAccountActivated();
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);
showGenericErrorDialog(status);
}
@ -65,7 +67,7 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
public void onIsAccountActivated(
AccountCreator creator, AccountCreator.Status status, String resp) {
Log.i(
"[Email Account Validation] onIsAccountActivated status is "
"[Email Account Validation Assistant] onIsAccountActivated status is "
+ status);
if (status.equals(AccountCreator.Status.AccountActivated)) {
createProxyConfigAndLeaveAssistant();
@ -87,7 +89,7 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
@Override
protected void onResume() {
super.onResume();
mAccountCreator.addListener(mListener);
getAccountCreator().addListener(mListener);
// Prevent user to go back, it won't be able to come back here after...
mBack.setEnabled(false);
@ -96,6 +98,6 @@ public class EmailAccountValidationAssistantActivity extends AssistantActivity {
@Override
protected void 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.TextView;
import androidx.annotation.Nullable;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.Core;
import org.linphone.core.TransportType;
import org.linphone.core.tools.Log;
public class GenericConnectionAssistantActivity extends AssistantActivity implements TextWatcher {
private TextView mLogin;
@ -63,24 +67,31 @@ public class GenericConnectionAssistantActivity extends AssistantActivity implem
}
private void configureAccount() {
mAccountCreator.setUsername(mUsername.getText().toString());
mAccountCreator.setDomain(mDomain.getText().toString());
mAccountCreator.setPassword(mPassword.getText().toString());
mAccountCreator.setDisplayName(mDisplayName.getText().toString());
Core core = LinphoneManager.getCore();
if (core != null) {
Log.i("[Generic Connection Assistant] Reloading configuration with default");
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()) {
case R.id.transport_udp:
mAccountCreator.setTransport(TransportType.Udp);
accountCreator.setTransport(TransportType.Udp);
break;
case R.id.transport_tcp:
mAccountCreator.setTransport(TransportType.Tcp);
accountCreator.setTransport(TransportType.Tcp);
break;
case R.id.transport_tls:
mAccountCreator.setTransport(TransportType.Tls);
accountCreator.setTransport(TransportType.Tls);
break;
}
createProxyConfigAndLeaveAssistant();
createProxyConfigAndLeaveAssistant(true);
}
@Override

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
private ClipboardManager mClipboard;
private int mActivationCodeLength;
private boolean mIsLinking;
private boolean mIsLinking = false, mIsLogin = false;
private AccountCreatorListenerStub mListener;
@Override
@ -50,20 +50,21 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
setContentView(R.layout.assistant_phone_account_validation);
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
&& getIntent().getBooleanExtra("isLinkingVerification", false)) {
mIsLinking = true;
findViewById(R.id.title_account_linking).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.title_account_activation).setVisibility(View.VISIBLE);
findViewById(R.id.title_account_creation).setVisibility(View.VISIBLE);
}
mActivationCodeLength =
getResources().getInteger(R.integer.phone_number_validation_code_length);
TextView phoneNumber = findViewById(R.id.phone_number);
phoneNumber.setText(mAccountCreator.getPhoneNumber());
phoneNumber.setText(getAccountCreator().getPhoneNumber());
mSmsCode = findViewById(R.id.sms_code);
mSmsCode.addTextChangedListener(
@ -87,21 +88,27 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
new View.OnClickListener() {
@Override
public void onClick(View v) {
AccountCreator accountCreator = getAccountCreator();
mFinishCreation.setEnabled(false);
mAccountCreator.setActivationCode(mSmsCode.getText().toString());
accountCreator.setActivationCode(mSmsCode.getText().toString());
AccountCreator.Status status;
if (mIsLinking) {
status = mAccountCreator.activateAlias();
status = accountCreator.activateAlias();
} else if (mIsLogin) {
status = accountCreator.loginLinphoneAccount();
} else {
status = mAccountCreator.activateAccount();
status = accountCreator.activateAccount();
}
if (status != AccountCreator.Status.RequestOk) {
Log.e(
"[Phone Account Validation] "
+ (mIsLinking
? "linkAccount"
: "activateAccount" + " returned ")
: (mIsLogin
? "loginLinphoneAccount"
: "activateAccount")
+ " returned ")
+ status);
mFinishCreation.setEnabled(true);
showGenericErrorDialog(status);
@ -118,12 +125,7 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
if (status.equals(AccountCreator.Status.AccountActivated)) {
createProxyConfigAndLeaveAssistant();
} else {
mFinishCreation.setEnabled(true);
showGenericErrorDialog(status);
if (status.equals(AccountCreator.Status.WrongActivationCode)) {
// TODO do something so the server re-send a SMS
}
onError(status);
}
}
@ -135,12 +137,20 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
LinphonePreferences.instance().setLinkPopupTime("");
goToLinphoneActivity();
} else {
mFinishCreation.setEnabled(true);
showGenericErrorDialog(status);
onError(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
protected void onResume() {
super.onResume();
mAccountCreator.addListener(mListener);
getAccountCreator().addListener(mListener);
// Prevent user to go back, it won't be able to come back here after...
mBack.setEnabled(false);
@ -173,6 +183,15 @@ public class PhoneAccountValidationAssistantActivity extends AssistantActivity {
@Override
protected void 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
* 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.STREAM_RING;
@ -54,8 +54,6 @@ import org.linphone.receivers.HeadsetReceiver;
import org.linphone.settings.LinphonePreferences;
public class AndroidAudioManager {
private static final int LINPHONE_VOLUME_STREAM = STREAM_VOICE_CALL;
private Context mContext;
private AudioManager mAudioManager;
private Call mRingingCall;
@ -415,10 +413,22 @@ public class AndroidAudioManager {
}
private void adjustVolume(int i) {
// starting from ICS, volume must be adjusted by the application, at least for
// STREAM_VOICE_CALL volume stream
if (mAudioManager.isVolumeFixed()) {
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(
LINPHONE_VOLUME_STREAM,
stream,
i < 0 ? AudioManager.ADJUST_LOWER : AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI);
}
@ -428,6 +438,7 @@ public class AndroidAudioManager {
public synchronized void bluetoothHeadetConnectionChanged(boolean connected) {
mIsBluetoothHeadsetConnected = connected;
mAudioManager.setBluetoothScoOn(connected);
LinphoneManager.getCallManager().refreshInCallActions();
}
public synchronized void bluetoothHeadetAudioConnectionChanged(boolean connected) {
@ -475,8 +486,10 @@ public class AndroidAudioManager {
new Thread() {
@Override
public void run() {
boolean resultAcknoledged;
Log.i("[Audio Manager] [Bluetooth] SCO start/stop thread started");
boolean resultAcknowledged;
int retries = 0;
do {
try {
Thread.sleep(200);
@ -496,14 +509,90 @@ public class AndroidAudioManager {
+ retries);
mAudioManager.stopBluetoothSco();
}
resultAcknoledged = isUsingBluetoothAudioRoute() == enable;
resultAcknowledged = isUsingBluetoothAudioRoute() == enable;
retries++;
}
} while (!resultAcknoledged && retries < 10);
} while (!resultAcknowledged && retries < 10);
}
}.start();
}
public void bluetoothAdapterStateChanged() {
if (mBluetoothAdapter.isEnabled()) {
Log.i("[Audio Manager] [Bluetooth] Adapter enabled");
mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false;
BluetoothProfile.ServiceListener bluetoothServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
Log.i("[Audio Manager] [Bluetooth] HEADSET profile connected");
mBluetoothHeadset = (BluetoothHeadset) proxy;
List<BluetoothDevice> devices =
mBluetoothHeadset.getConnectedDevices();
if (devices.size() > 0) {
Log.i(
"[Audio Manager] [Bluetooth] A device is already connected");
bluetoothHeadetConnectionChanged(true);
}
Log.i("[Audio Manager] [Bluetooth] Registering bluetooth receiver");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(
BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
Intent sticky =
mContext.registerReceiver(mBluetoothReceiver, filter);
Log.i("[Audio Manager] [Bluetooth] Bluetooth receiver registered");
int state =
sticky.getIntExtra(
AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connected");
bluetoothHeadetScoConnectionChanged(true);
} else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO disconnected");
bluetoothHeadetScoConnectionChanged(false);
} else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connecting");
} else if (state == AudioManager.SCO_AUDIO_STATE_ERROR) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connection error");
} else {
Log.w(
"[Audio Manager] [Bluetooth] Bluetooth headset unknown SCO state changed: "
+ state);
}
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
Log.i("[Audio Manager] [Bluetooth] HEADSET profile disconnected");
mBluetoothHeadset = null;
mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false;
}
}
};
mBluetoothAdapter.getProfileProxy(
mContext, bluetoothServiceListener, BluetoothProfile.HEADSET);
} else {
Log.w("[Audio Manager] [Bluetooth] Adapter disabled");
}
}
private void startBluetooth() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null) {
@ -513,79 +602,12 @@ public class AndroidAudioManager {
} else {
Log.w("[Audio Manager] [Bluetooth] SCO not available off call !");
}
if (mBluetoothAdapter.isEnabled()) {
Log.i("[Audio Manager] [Bluetooth] Adapter enabled");
mBluetoothReceiver = new BluetoothReceiver();
mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false;
BluetoothProfile.ServiceListener bluetoothServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
Log.i("[Audio Manager] [Bluetooth] HEADSET profile connected");
mBluetoothHeadset = (BluetoothHeadset) proxy;
mBluetoothReceiver = new BluetoothReceiver();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothReceiver, filter);
List<BluetoothDevice> devices =
mBluetoothHeadset.getConnectedDevices();
if (devices.size() > 0) {
Log.i(
"[Audio Manager] [Bluetooth] A device is already connected");
bluetoothHeadetConnectionChanged(true);
}
Log.i(
"[Audio Manager] [Bluetooth] Registering bluetooth receiver");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
filter.addAction(
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(
BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
Intent sticky =
mContext.registerReceiver(mBluetoothReceiver, filter);
int state =
sticky.getIntExtra(
AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connected");
bluetoothHeadetScoConnectionChanged(true);
} else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO disconnected");
bluetoothHeadetScoConnectionChanged(false);
} else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connecting");
} else if (state == AudioManager.SCO_AUDIO_STATE_ERROR) {
Log.i(
"[Audio Manager] [Bluetooth] Bluetooth headset SCO connection error");
} else {
Log.w(
"[Audio Manager] [Bluetooth] Bluetooth headset unknown SCO state changed: "
+ state);
}
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
Log.i(
"[Audio Manager] [Bluetooth] HEADSET profile disconnected");
mBluetoothHeadset = null;
mIsBluetoothHeadsetConnected = false;
mIsBluetoothHeadsetScoConnected = false;
}
}
};
mBluetoothAdapter.getProfileProxy(
mContext, bluetoothServiceListener, BluetoothProfile.HEADSET);
}
bluetoothAdapterStateChanged();
}
}

View file

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

View file

@ -27,6 +27,7 @@ import android.os.Bundle;
import android.view.KeyEvent;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -36,9 +37,13 @@ import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.R;
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.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.Call.State;
@ -47,10 +52,6 @@ import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
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 {
private TextView mName, mNumber;
@ -63,6 +64,7 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Compatibility.setShowWhenLocked(this, true);
Compatibility.setTurnScreenOn(this, true);
@ -122,13 +124,13 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
Core core, Call call, State state, String message) {
if (call == mCall) {
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,
// CallActivity.class));
}
}
if (LinphoneManager.getCore().getCallsNb() == 0) {
if (state == State.End || state == State.Released) {
finish();
}
}
@ -230,6 +232,7 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
mAlreadyAcceptedOrDeniedCall = true;
mCall.terminate();
finish();
}
private void answer() {
@ -276,8 +279,9 @@ public class CallIncomingActivity extends LinphoneGenericActivity {
Log.i("[Permission] Asking for read phone state");
permissionsList.add(Manifest.permission.READ_PHONE_STATE);
}
if (LinphonePreferences.instance().shouldInitiateVideoCall()
|| LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()) {
if (LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()
&& mCall != null
&& mCall.getRemoteParams().videoEnabled()) {
if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for 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.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.dialer.views.AddressType;
import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.AddressType;
/** Handle call updating, reinvites. */
public class CallManager {
@ -87,32 +87,26 @@ public class CallManager {
public void switchCamera() {
Core core = LinphoneManager.getCore();
try {
String currentDevice = core.getVideoDevice();
String[] devices = core.getVideoDevicesList();
int index = 0;
for (String d : devices) {
if (d.equals(currentDevice)) {
break;
}
index++;
}
if (core == null) return;
String newDevice;
if (index == 1) newDevice = devices[0];
else if (devices.length > 1) newDevice = devices[1];
else newDevice = devices[index];
core.setVideoDevice(newDevice);
String currentDevice = core.getVideoDevice();
Log.i("[Call Manager] Current camera device is " + currentDevice);
Call call = core.getCurrentCall();
if (call == null) {
Log.w("[Call Manager] Trying to switch camera while not in call");
return;
String[] devices = core.getVideoDevicesList();
for (String d : devices) {
if (!d.equals(currentDevice) && !d.equals("StaticImage: Static picture")) {
Log.i("[Call Manager] New camera device will be " + d);
core.setVideoDevice(d);
break;
}
call.update(null);
} catch (ArithmeticException ae) {
Log.e("[Call Manager] [Video] Cannot switch camera: no camera");
}
Call call = core.getCurrentCall();
if (call == null) {
Log.i("[Call Manager] Switching camera while not in call");
return;
}
call.update(null);
}
public boolean acceptCall(Call call) {

View file

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

View file

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

View file

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

View file

@ -288,9 +288,19 @@ public class CallStatusBarFragment extends Fragment {
public void refreshStatusItems(final Call call) {
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);
return;
}
MediaEncryption mediaEncryption = call.getCurrentParams().getMediaEncryption();
mEncryption.setVisibility(View.VISIBLE);
if (mediaEncryption == MediaEncryption.SRTP
|| (mediaEncryption == MediaEncryption.ZRTP
&& call.getAuthenticationTokenVerified())

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.annotation.SuppressLint;
import android.content.Context;
@ -30,6 +30,8 @@ import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.core.Core;
import org.linphone.core.ProxyConfig;
import org.linphone.dialer.views.AddressAware;
import org.linphone.dialer.views.AddressText;
import org.linphone.settings.LinphonePreferences;
@SuppressLint("AppCompatCustomView")

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.content.Context;
import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
public interface CallIncomingButtonListener {
void onAction();

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.content.Context;
import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.content.Context;
import android.content.Intent;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.content.Context;
import android.util.AttributeSet;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.view.WindowManager;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.call.views;
import android.content.Context;
import android.content.Intent;

View file

@ -20,6 +20,7 @@
package org.linphone.chat;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -258,7 +259,18 @@ public class ChatActivity extends MainActivity {
ArrayList<ContactAddress> participants,
String subject,
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();
if (peerAddress != null) {
extras.putSerializable("RemoteSipUri", peerAddress.asStringUriOnly());

View file

@ -50,6 +50,7 @@ import java.util.List;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.Content;
@ -57,7 +58,6 @@ import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
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;
public final CheckBox delete;
public boolean isEditionEnabled;
private Context mContext;
private ChatMessageViewHolderClickListener mListener;
@ -275,7 +276,11 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
new View.OnClickListener() {
@Override
public void onClick(View v) {
openFile(filePath);
if (isEditionEnabled) {
ChatMessageViewHolder.this.onClick(v);
} else {
openFile(filePath);
}
}
});
} else {
@ -315,11 +320,15 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
new View.OnClickListener() {
@Override
public void onClick(View v) {
Content c = (Content) v.getTag();
if (!message.isFileTransferInProgress()) {
message.downloadContent(c);
if (isEditionEnabled) {
ChatMessageViewHolder.this.onClick(v);
} else {
message.cancelFileTransfer();
Content c = (Content) v.getTag();
if (!message.isFileTransferInProgress()) {
message.downloadContent(c);
} else {
message.cancelFileTransfer();
}
}
}
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,16 +26,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.Participant;
@ -43,7 +39,6 @@ import org.linphone.core.ParticipantDevice;
import org.linphone.utils.LinphoneUtils;
public class DevicesFragment extends Fragment {
private TextView mTitle;
private ExpandableListView mExpandableList;
private DevicesAdapter mAdapter;
@ -114,9 +109,6 @@ public class DevicesFragment extends Fragment {
initChatRoom();
mTitle = view.findViewById(R.id.title);
initHeader();
ImageView backButton = view.findViewById(R.id.back);
backButton.setOnClickListener(
new View.OnClickListener() {
@ -146,24 +138,6 @@ public class DevicesFragment extends Fragment {
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() {
if (mAdapter == null) {
mAdapter = new DevicesAdapter(getActivity());

View file

@ -30,9 +30,9 @@ import org.linphone.LinphoneContext;
import org.linphone.R;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.ChatRoom;
import org.linphone.core.Participant;
import org.linphone.views.ContactAvatar;
class GroupInfoAdapter extends RecyclerView.Adapter<GroupInfoViewHolder> {
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.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@ -42,6 +41,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
@ -56,7 +56,6 @@ import org.linphone.core.Factory;
import org.linphone.core.Participant;
import org.linphone.core.tools.Log;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.LinphoneLinearLayoutManager;
public class GroupInfoFragment extends Fragment {
private ImageView mConfirmButton;
@ -146,7 +145,11 @@ public class GroupInfoFragment extends Fragment {
new View.OnClickListener() {
@Override
public void onClick(View view) {
((ChatActivity) getActivity()).goBack();
if (mIsAlreadyCreatedGroup) {
((ChatActivity) getActivity()).goBack();
} else {
goBackToChatCreationFragment();
}
}
});
@ -262,6 +265,16 @@ public class GroupInfoFragment extends Fragment {
public void onSubjectChanged(ChatRoom cr, EventLog event_log) {
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) {
@ -300,26 +313,14 @@ public class GroupInfoFragment extends Fragment {
}
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())
.showChatRoomCreation(
mGroupChatRoomAddress,
mParticipants,
mSubject,
mIsEncryptionEnabled,
true);
}
((ChatActivity) getActivity())
.showChatRoomCreation(
mGroupChatRoomAddress,
mParticipants,
mSubject,
mIsEncryptionEnabled,
true,
!mIsAlreadyCreatedGroup);
}
private void refreshParticipantsList() {
@ -485,7 +486,9 @@ public class GroupInfoFragment extends Fragment {
}
Address[] participantsToAdd = new Address[toAdd.size()];
toAdd.toArray(participantsToAdd);
mChatRoom.addParticipants(participantsToAdd);
if (!mChatRoom.addParticipants(participantsToAdd)) {
// TODO error
}
// Pop back stack to go back to the Messages fragment
getFragmentManager().popBackStack();
}

View file

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

View file

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

View file

@ -183,7 +183,7 @@ class ApiTwentyOnePlus {
}
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)
.setContentTitle(title)
@ -201,6 +201,7 @@ class ApiTwentyOnePlus {
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setNumber(count)
.build();
}

View file

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

View file

@ -23,6 +23,7 @@ import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.content.Context;
import android.os.PowerManager;
import android.service.notification.StatusBarNotification;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
@ -111,4 +112,8 @@ class ApiTwentyThreePlus {
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.FragmentTransaction;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.ContentProviderClient;
@ -30,6 +31,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import org.linphone.core.Address;
import org.linphone.mediastream.Version;
import org.linphone.notifications.Notifiable;
@ -103,11 +105,12 @@ public class Compatibility {
}
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)) {
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(
@ -308,4 +311,12 @@ public class Compatibility {
}
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.ContentResolver;
import android.content.ContentUris;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
@ -30,6 +31,8 @@ import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import org.linphone.LinphoneContext;
@ -41,10 +44,12 @@ class AndroidContact implements Serializable {
private String mAndroidRawId;
private boolean isAndroidRawIdLinphone;
private final transient ArrayList<ContentProviderOperation> mChangesToCommit;
private byte[] mTempPicture;
AndroidContact() {
mChangesToCommit = new ArrayList<>();
isAndroidRawIdLinphone = false;
mTempPicture = null;
}
String getAndroidId() {
@ -78,6 +83,12 @@ class AndroidContact implements Serializable {
String rawId = String.valueOf(ContentUris.parseId(results[0].uri));
if (mAndroidId == null) {
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 =
new String[] {ContactsContract.RawContacts.CONTACT_ID};
@ -146,6 +157,7 @@ class AndroidContact implements Serializable {
}
void deleteAndroidContact() {
Log.i("[Contact] Deleting Android contact ", this);
ContactsManager.getInstance().delete(mAndroidId);
}
@ -550,33 +562,44 @@ class AndroidContact implements Serializable {
return;
}
if (mAndroidId == null) {
Log.i("[Contact] Setting picture to new contact.");
addChangesToCommit(
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());
if (mAndroidRawId == null) {
Log.w("[Contact] Can't set picture for not already created contact, will do it later");
mTempPicture = photo;
} else {
Log.i(
"[Contact] Setting picture to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo)
.withValue(ContactsContract.Data.IS_PRIMARY, 1)
.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1)
.build());
"[Contact] Setting picture to an already created raw contact [",
mAndroidRawId,
"]");
try {
long rawId = Long.parseLong(mAndroidRawId);
Uri rawContactPhotoUri =
Uri.withAppendedPath(
ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawId),
RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
if (rawContactPhotoUri != null) {
ContentResolver resolver =
LinphoneContext.instance().getApplicationContext().getContentResolver();
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) {
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<>();
AsyncContactsData data = new AsyncContactsData();
List<String> nativeIds = new ArrayList<>();
@ -111,10 +96,12 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
LinphoneContact contact = (LinphoneContact) friend.getUserData();
if (contact != null) {
contact.clearAddresses();
if (contact.getAndroidId() != null) {
contact.clearAddresses();
androidContactsCache.put(contact.getAndroidId(), contact);
nativeIds.add(contact.getAndroidId());
} else {
data.contacts.add(contact);
}
} else {
if (friend.getRefKey() != null) {
@ -134,35 +121,57 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
}
}
if (c != null) {
Log.i("[Contacts Manager] Found " + c.getCount() + " entries in cursor");
while (c.moveToNext()) {
if (isCancelled()) {
Log.w("[Contacts Manager] Task cancelled");
return data;
}
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
boolean starred =
c.getInt(c.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1;
LinphoneContact contact = androidContactsCache.get(id);
if (contact == null) {
Log.d(
"[Contacts Manager] Creating LinphoneContact with native ID "
+ id
+ ", favorite flag is "
+ starred);
nativeIds.add(id);
contact = new LinphoneContact();
contact.setAndroidId(id);
contact.setIsFavourite(starred);
androidContactsCache.put(id, contact);
}
contact.syncValuesFromAndroidCusor(c);
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) {
Log.i("[Contacts Manager] Found " + c.getCount() + " entries in cursor");
while (c.moveToNext()) {
if (isCancelled()) {
Log.w("[Contacts Manager] Task cancelled");
return data;
}
try {
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
boolean starred =
c.getInt(c.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1;
LinphoneContact contact = androidContactsCache.get(id);
if (contact == null) {
Log.d(
"[Contacts Manager] Creating LinphoneContact with native ID "
+ id
+ ", favorite flag is "
+ starred);
nativeIds.add(id);
contact = new LinphoneContact();
contact.setAndroidId(id);
contact.setIsFavourite(starred);
androidContactsCache.put(id, contact);
}
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();
for (FriendList list : friendLists) {
@ -188,7 +197,13 @@ class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.Asyn
}
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) {
if (isCancelled()) {
Log.w("[Contacts Manager] Task cancelled");

View file

@ -36,6 +36,7 @@ import android.widget.TableLayout;
import android.widget.TextView;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend;
@ -50,7 +51,6 @@ import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactDetailsFragment extends Fragment implements ContactsUpdatedListener {
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);
deleteContact.setOnClickListener(
new OnClickListener() {
@ -359,6 +364,8 @@ public class ContactDetailsFragment extends Fragment implements ContactsUpdatedL
private void goToChat(String tag, boolean isSecured) {
Core core = LinphoneManager.getCore();
if (core == null) return;
Address participant = Factory.instance().createAddress(tag);
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.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.InputType;
@ -53,12 +52,13 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactEditorFragment extends Fragment {
private static final int ADD_PHOTO = 1337;
@ -136,12 +136,15 @@ public class ContactEditorFragment extends Fragment {
if (mIsNewContact) {
boolean areAllFielsEmpty = true;
for (LinphoneNumberOrAddress nounoa : mNumbersAndAddresses) {
if (nounoa.getValue() != null && !nounoa.getValue().equals("")) {
String value = nounoa.getValue();
if (value != null && !value.trim().isEmpty()) {
areAllFielsEmpty = false;
break;
}
}
if (areAllFielsEmpty) {
Log.i(
"[Contact Editor] All SIP and phone fields are empty, aborting");
getFragmentManager().popBackStackImmediate();
return;
}
@ -154,37 +157,36 @@ public class ContactEditorFragment extends Fragment {
true);
if (mPhotoToAdd != null) {
Log.i("[Contact Editor] Found picture to set to contact");
mContact.setPhoto(mPhotoToAdd);
}
for (LinphoneNumberOrAddress noa : mNumbersAndAddresses) {
if (noa.getValue() == null || noa.getValue().isEmpty()) {
if (noa.getOldValue() != null && !noa.getOldValue().isEmpty()) {
Log.i("[Contact Editor] Removing number " + noa.getOldValue());
String value = noa.getValue();
String oldValue = noa.getOldValue();
if (value == null || value.trim().isEmpty()) {
if (oldValue != null && !oldValue.isEmpty()) {
Log.i("[Contact Editor] Removing number: ", oldValue);
mContact.removeNumberOrAddress(noa);
}
} else {
if (noa.getOldValue() != null
&& noa.getOldValue().equals(noa.getValue())) {
Log.i(
"[Contact Editor] Keeping existing number "
+ noa.getValue());
if (oldValue != null && oldValue.equals(value)) {
Log.i("[Contact Editor] Keeping existing number: ", value);
continue;
}
if (noa.isSIPAddress()) {
noa.setValue(
LinphoneUtils.getFullAddressFromUsername(
noa.getValue()));
noa.setValue(LinphoneUtils.getFullAddressFromUsername(value));
}
Log.i("[Contact Editor] Adding new number " + noa.getValue());
Log.i("[Contact Editor] Adding new number: ", value);
mContact.addOrUpdateNumberOrAddress(noa);
}
}
if (!mOrganization.getText().toString().isEmpty() || !mIsNewContact) {
Log.i("[Contact Editor] Setting organization field: ", mOrganization);
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
// list: contacts content observer may not be notified if contacts sync
// is disabled at system level
Log.i(
"[Contact Editor] New contact created, starting fetch contacts task");
ContactsManager.getInstance().fetchContactsAsync();
}
@ -306,9 +310,18 @@ public class ContactEditorFragment extends Fragment {
new OnClickListener() {
@Override
public void onClick(View view) {
pickImage();
((ContactsActivity) getActivity())
.requestPermissionIfNotGranted(Manifest.permission.CAMERA);
ContactsActivity contactsActivity = ((ContactsActivity) getActivity());
if (contactsActivity != null) {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
if (contactsActivity.checkPermissions(permissions)) {
pickImage();
} else {
contactsActivity.requestPermissionsIfNotGranted(permissions);
}
}
}
});
@ -386,15 +399,18 @@ public class ContactEditorFragment extends Fragment {
editContactPicture(null, bm);
} else if (data != null && data.getData() != null) {
Uri selectedImageUri = data.getData();
try {
Bitmap selectedImage =
MediaStore.Images.Media.getBitmap(
getActivity().getContentResolver(), selectedImageUri);
selectedImage =
Bitmap.createScaledBitmap(selectedImage, PHOTO_SIZE, PHOTO_SIZE, false);
editContactPicture(null, selectedImage);
} catch (IOException e) {
Log.e(e);
String filePath = FileUtils.getRealPathFromURI(getActivity(), selectedImageUri);
if (filePath != null) {
editContactPicture(filePath, null);
} else {
try {
Bitmap selectedImage =
MediaStore.Images.Media.getBitmap(
getActivity().getContentResolver(), selectedImageUri);
editContactPicture(null, selectedImage);
} catch (IOException e) {
Log.e("[Contact Editor] IO error: ", e);
}
}
} else if (mPickedPhotoForContactUri != null) {
String filePath = mPickedPhotoForContactUri.getPath();
@ -426,10 +442,12 @@ public class ContactEditorFragment extends Fragment {
}
}
if (mContact != null) {
ContactAvatar.displayAvatar(mContact, mView.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar("", mView.findViewById(R.id.avatar_layout));
if (mPhotoToAdd == null) {
if (mContact != null) {
ContactAvatar.displayAvatar(mContact, mView.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar("", mView.findViewById(R.id.avatar_layout));
}
}
mSipAddresses = initSipAddressFields(mContact);
@ -438,27 +456,24 @@ public class ContactEditorFragment extends Fragment {
private void pickImage() {
mPickedPhotoForContactUri = null;
final List<Intent> cameraIntents = new ArrayList<>();
final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
List<Intent> cameraIntents = new ArrayList<>();
// 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 =
new File(
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);
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);
cameraIntents.add(captureIntent);
final Intent galleryIntent = new Intent();
galleryIntent.setType("image/*");
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
final Intent chooserIntent =
Intent chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.image_picker_title));
chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[] {}));
@ -467,44 +482,59 @@ public class ContactEditorFragment extends Fragment {
}
private void editContactPicture(String filePath, Bitmap image) {
int orientation = ExifInterface.ORIENTATION_NORMAL;
if (image == null) {
Log.i(
"[Contact Editor] Bitmap is null, trying to decode image from file [",
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;
int size = getThumbnailSize();
if (size > 0) {
scaledPhoto = Bitmap.createScaledBitmap(image, size, size, false);
} else {
scaledPhoto = Bitmap.createBitmap(image);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
image = ImageUtils.rotateImage(image, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
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();
scaledPhoto.compress(Bitmap.CompressFormat.PNG, 0, stream);
mContactPicture.setImageBitmap(scaledPhoto);
image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
mPhotoToAdd = stream.toByteArray();
}
private int getThumbnailSize() {
int value = -1;
Cursor c =
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;
Bitmap roundPicture = ImageUtils.getRoundBitmap(image);
ContactAvatar.displayAvatar(roundPicture, mView.findViewById(R.id.avatar_layout));
image.recycle();
}
private LinearLayout initNumbersFields(final LinphoneContact contact) {

View file

@ -102,6 +102,15 @@ public class ContactsActivity extends MainActivity {
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
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View file

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

View file

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

View file

@ -55,7 +55,8 @@ import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
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 final ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners;
private MagicSearch mMagicSearch;
@ -79,6 +80,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
mMagicSearch = LinphoneManager.getCore().createMagicSearch();
mMagicSearch.setLimitedSearch(false); // Do not limit the number of results
}
LinphoneContext.instance().addCoreStartedListener(this);
}
public void addContactsListener(ContactsUpdatedListener listener) {
@ -104,6 +107,13 @@ public class ContactsManager extends ContentObserver implements FriendListListen
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() {
return mContacts;
}
@ -122,6 +132,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
public void destroy() {
mContext.getContentResolver().unregisterContentObserver(this);
LinphoneContext.instance().removeCoreStartedListener(this);
if (mLoadContactTask != null) {
mLoadContactTask.cancel(true);
@ -149,10 +160,12 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mLoadContactTask != null) {
mLoadContactTask.cancel(true);
}
if (!hasReadContactsAccess()) {
Log.w("[Contacts Manager] Can't fetch contact without READ permission");
return;
Log.w(
"[Contacts Manager] Can't fetch native contacts without READ_CONTACTS permission");
}
mLoadContactTask = new AsyncContactsLoader(mContext);
mContactsFetchedOnce = true;
mLoadContactTask.executeOnExecutor(THREAD_POOL_EXECUTOR);
@ -212,6 +225,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) {
return false;
}
boolean contactsR =
(PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
@ -226,6 +240,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) {
return false;
}
return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
.checkPermission(
@ -236,6 +251,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
if (mContext == null) {
return false;
}
return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
.checkPermission(
@ -264,10 +280,6 @@ public class ContactsManager extends ContentObserver implements FriendListListen
}
}
}
if (mContext != null && getContacts().isEmpty() && hasReadContactsAccess()) {
fetchContactsAsync();
}
}
private void makeContactAccountVisible() {
@ -366,6 +378,11 @@ public class ContactsManager extends ContentObserver implements FriendListListen
}
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()) {
return findContactFromPhoneNumber(username);
}
@ -421,6 +438,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
}
public String getAddressOrNumberForAndroidContact(ContentResolver resolver, Uri contactUri) {
if (resolver == null || contactUri == null) return null;
// Phone Numbers
String[] projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor c = resolver.query(contactUri, projection, null, null, null);
@ -431,8 +450,8 @@ public class ContactsManager extends ContentObserver implements FriendListListen
c.close();
return number;
}
c.close();
}
c.close();
projection = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS};
c = resolver.query(contactUri, projection, null, null, null);
@ -444,12 +463,14 @@ public class ContactsManager extends ContentObserver implements FriendListListen
c.close();
return address;
}
c.close();
}
c.close();
return null;
}
private synchronized boolean refreshSipContact(Friend lf) {
if (lf == null) return false;
LinphoneContact contact = (LinphoneContact) lf.getUserData();
if (contact != null) {
@ -486,6 +507,7 @@ public class ContactsManager extends ContentObserver implements FriendListListen
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (String id : ids) {
Log.i("[Contacts Manager] Adding Android contact id ", id, " to batch removal");
String[] args = new String[] {id};
ops.add(
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.Friend;
import org.linphone.core.FriendCapability;
import org.linphone.core.FriendList;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.SubscribePolicy;
@ -195,7 +194,7 @@ public class LinphoneContact extends AndroidContact
}
private void setPhotoUri(Uri uri) {
if (uri.equals(mPhotoUri)) return;
if (uri != null && uri.equals(mPhotoUri)) return;
mPhotoUri = uri;
}
@ -204,7 +203,7 @@ public class LinphoneContact extends AndroidContact
}
private void setThumbnailUri(Uri uri) {
if (uri.equals(mThumbnailUri)) return;
if (uri != null && uri.equals(mThumbnailUri)) return;
mThumbnailUri = uri;
}
@ -216,14 +215,16 @@ public class LinphoneContact extends AndroidContact
if (noa == null) return;
boolean found = false;
String normalizedPhone = noa.getNormalizedPhone();
// Check for duplicated phone numbers but with different formats
for (LinphoneNumberOrAddress number : mAddresses) {
if (!number.isSIPAddress()) {
if ((!noa.isSIPAddress()
&& noa.getNormalizedPhone().equals(number.getNormalizedPhone()))
&& normalizedPhone != null
&& normalizedPhone.equals(number.getNormalizedPhone()))
|| (noa.isSIPAddress()
&& noa.getValue().equals(number.getNormalizedPhone()))
|| (noa.getNormalizedPhone().equals(number.getValue()))) {
|| (normalizedPhone != null && normalizedPhone.equals(number.getValue()))) {
Log.d("[Linphone Contact] Duplicated entry detected: " + noa);
found = true;
break;
@ -247,7 +248,8 @@ public class LinphoneContact extends AndroidContact
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
if (noa.isSIPAddress()) {
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
// address may have a ;gruu= at the end...
return true;
@ -274,7 +276,8 @@ public class LinphoneContact extends AndroidContact
}
LinphoneNumberOrAddress toRemove = null;
for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue())
if (noa.getOldValue() != null
&& noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) {
toRemove = address;
break;
@ -305,7 +308,8 @@ public class LinphoneContact extends AndroidContact
}
}
for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue())
if (noa.getOldValue() != null
&& noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) {
address.setValue(noa.getValue());
break;
@ -352,9 +356,10 @@ public class LinphoneContact extends AndroidContact
if (mFriend.getVcard() != null) {
mFriend.getVcard().setFamilyName(mLastName);
mFriend.getVcard().setGivenName(mFirstName);
}
if (mOrganization != null) {
mFriend.getVcard().setOrganization(mOrganization);
if (mOrganization != null) {
mFriend.getVcard().setOrganization(mOrganization);
}
}
if (!created) {
@ -395,12 +400,12 @@ public class LinphoneContact extends AndroidContact
}
public void deleteFriend() {
if (mFriend == null) return;
Core core = LinphoneManager.getCore();
if (mFriend != null && core != null) {
for (FriendList list : core.getFriendsLists()) {
list.removeFriend(mFriend);
}
}
if (core == null) return;
Log.i("[Contact] Deleting friend ", mFriend.getName(), " for contact ", this);
mFriend.remove();
}
public void createOrUpdateFriendFromNativeContact() {
@ -427,7 +432,9 @@ public class LinphoneContact extends AndroidContact
if (mFriend == null) return false;
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
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;
}
}
@ -449,14 +456,22 @@ public class LinphoneContact extends AndroidContact
}
public boolean hasPresenceModelForUriOrTelCapability(String uri, FriendCapability capability) {
if (mFriend == null) return false;
if (mFriend.getPresenceModelForUriOrTel(uri) != null) {
return mFriend.getPresenceModelForUriOrTel(uri).hasCapability(capability);
if (mFriend == null || uri == null) return false;
PresenceModel presence = mFriend.getPresenceModelForUriOrTel(uri);
if (presence != null) {
return presence.hasCapability(capability);
} else {
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
if (getContactFromPresenceModelForUriOrTel(noa.getValue()).equals(uri)) {
return mFriend.getPresenceModelForUriOrTel(noa.getValue())
.hasCapability(capability);
String value = noa.getValue();
if (value != null) {
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 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);
setFullName(displayName);
}
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 + ")");
addNumberOrAddress(new LinphoneNumberOrAddress(data1, data4));
} else if (ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE.equals(mime)
@ -560,12 +581,27 @@ public class LinphoneContact extends AndroidContact
.getApplicationContext()
.getString(R.string.linphone_address_mime_type)
.equals(mime)) {
if (data1 == null) {
Log.e("[Linphone Contact] SIP address is null !");
return;
}
Log.d("[Linphone Contact] Found SIP address " + data1);
addNumberOrAddress(new LinphoneNumberOrAddress(data1, true));
} 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);
setOrganization(data1, false);
} 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);
setFirstNameAndLastName(data2, data3, false);
} else {
@ -592,7 +628,9 @@ public class LinphoneContact extends AndroidContact
// Test presence of the value
PresenceModel pm = getFriend().getPresenceModelForUriOrTel(value);
// 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);
if (!isLinphoneAddressMimeEntryAlreadyExisting(value)) {
// 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() {
saveChangesCommited();
if (getAndroidId() != null) {
setThumbnailUri(getContactThumbnailPictureUri());
setPhotoUri(getContactPictureUri());
}
syncValuesFromAndroidContact(LinphoneContext.instance().getApplicationContext());
createOrUpdateFriend();
}
public void delete() {
deleteAndroidContact();
Log.i("[Contact] Deleting contact ", this);
if (isAndroidContact()) {
deleteAndroidContact();
}
if (isFriend()) {
deleteFriend();
}

View file

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

View file

@ -30,13 +30,14 @@ import java.util.Objects;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address;
import org.linphone.core.Core;
import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig;
import org.linphone.core.SearchResult;
import org.linphone.views.ContactAvatar;
public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactViewHolder> {
private List<SearchResult> mContacts;
@ -118,8 +119,20 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
holder.name.setText(searchResult.getAddress().getDisplayName());
}
}
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.getFullName() == null
&& contact.getFirstName() == null
@ -137,15 +150,6 @@ public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactVie
&& !searchResult.hasCapability(FriendCapability.LimeX3Dh))) {
// Disable row, contact doesn't have the required capabilities
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 {
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
* 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.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.linphone.R;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.ChatRoomSecurityLevel;
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 {
private static String generateAvatar(String displayName) {
String[] names = displayName.split(" ");
StringBuilder generatedAvatarText = new StringBuilder();
int count = 0;
for (String name : names) {
if (name != null && name.length() > 0) {
if (name != null && name.length() > 0 && count < 2) {
generatedAvatarText.append(name.charAt(0));
count += 1;
}
}
return generatedAvatarText.toString().toUpperCase();
@ -161,10 +137,7 @@ public class ContactAvatar {
Bitmap bm = ImageUtils.getRoundBitmapFromUri(v.getContext(), contact.getThumbnailUri());
if (bm != null) {
holder.contactPicture.setImageBitmap(bm);
holder.contactPicture.setVisibility(View.VISIBLE);
holder.generatedAvatar.setVisibility(View.GONE);
holder.generatedAvatarBackground.setVisibility(View.GONE);
displayAvatar(bm, holder);
} else if (generated_avatars) {
holder.generatedAvatar.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) {
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
* 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.view.LayoutInflater;

View file

@ -17,7 +17,7 @@
* 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.activities;
package org.linphone.dialer;
import android.Manifest;
import android.content.Intent;
@ -37,17 +37,18 @@ import java.util.ArrayList;
import java.util.Collection;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.activities.MainActivity;
import org.linphone.call.views.CallButton;
import org.linphone.contacts.ContactsActivity;
import org.linphone.contacts.ContactsManager;
import org.linphone.core.Call;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
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.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 {
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) {
Core core = LinphoneManager.getCore();
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()) {
preview.setVisibility(View.VISIBLE);
core.setNativePreviewWindowId(preview);
core.enableVideoPreview(true);
ImageView changeCamera = findViewById(R.id.video_preview_change_camera);
if (changeCamera != null && core.getVideoDevicesList().length > 1) {
if (core.getVideoDevicesList().length > 1) {
changeCamera.setVisibility(View.VISIBLE);
changeCamera.setOnClickListener(
new View.OnClickListener() {
@ -239,6 +241,7 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
}
} else {
preview.setVisibility(View.GONE);
changeCamera.setVisibility(View.GONE);
core.setNativePreviewWindowId(null);
core.enableVideoPreview(false);
}
@ -259,9 +262,7 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
@Override
public void onAddressChanged() {
Core core = LinphoneManager.getCore();
mAddContact.setEnabled(
core != null && core.getCallsNb() > 0 || !mAddress.getText().toString().equals(""));
mAddContact.setEnabled(!mAddress.getText().toString().isEmpty());
}
private void updateLayout() {
@ -273,6 +274,8 @@ public class DialerActivity extends MainActivity implements AddressText.AddressC
boolean atLeastOneCall = core.getCallsNb() > 0;
mStartCall.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE);
mAddContact.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE);
mAddContact.setEnabled(!mAddress.getText().toString().isEmpty());
if (!atLeastOneCall) {
if (core.getVideoActivationPolicy().getAutomaticallyInitiate()) {
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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.views;
package org.linphone.dialer.views;
public interface AddressAware {
void setAddressWidget(AddressText address);

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.dialer.views;
import android.annotation.SuppressLint;
import android.content.Context;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.dialer.views;
public interface AddressType {
CharSequence getText();

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.dialer.views;
import android.annotation.SuppressLint;
import android.app.AlertDialog;

View file

@ -17,7 +17,7 @@
* 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;
package org.linphone.dialer.views;
import android.annotation.SuppressLint;
import android.content.Context;

View file

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

View file

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

View file

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

View file

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

View file

@ -35,20 +35,22 @@ import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.views.ContactAvatar;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class HistoryDetailFragment extends Fragment {
private ImageView mAddToContacts;
@ -60,6 +62,7 @@ public class HistoryDetailFragment extends Fragment {
private ChatRoom mChatRoom;
private ChatRoomListenerStub mChatRoomCreationListener;
private ListView mLogsList;
private CoreListenerStub mListener;
@Override
public View onCreateView(
@ -157,7 +160,7 @@ public class HistoryDetailFragment extends Fragment {
mWaitLayout.setVisibility(View.GONE);
((HistoryActivity) getActivity()).displayChatRoomError();
Log.e(
"Group mChat room for address "
"[History Detail Fragment] Group mChat room for address "
+ cr.getPeerAddress()
+ " has failed !");
}
@ -165,16 +168,36 @@ public class HistoryDetailFragment extends Fragment {
};
mLogsList = view.findViewById(R.id.logs_list);
displayHistory();
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();
}
}
};
return view;
}
@Override
public void onResume() {
super.onResume();
LinphoneManager.getCore().addListener(mListener);
displayHistory();
}
@Override
public void onPause() {
if (mChatRoom != null) {
mChatRoom.removeListener(mChatRoomCreationListener);
}
LinphoneManager.getCore().removeListener(mListener);
super.onPause();
}
@ -234,45 +257,65 @@ public class HistoryDetailFragment extends Fragment {
private void goToChat(boolean isSecured) {
Core core = LinphoneManager.getCore();
if (core == null) return;
Address participant = Factory.instance().createAddress(mSipUri);
ChatRoom room =
core.findOneToOneChatRoom(
core.getDefaultProxyConfig().getContact(), participant, isSecured);
if (room != null) {
((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
} else {
ProxyConfig lpc = core.getDefaultProxyConfig();
if (lpc != null
&& lpc.getConferenceFactoryUri() != null
&& (isSecured || !LinphonePreferences.instance().useBasicChatRoomFor1To1())) {
mWaitLayout.setVisibility(View.VISIBLE);
ProxyConfig defaultProxyConfig = core.getDefaultProxyConfig();
ChatRoomParams params = core.createDefaultChatRoomParams();
params.enableEncryption(isSecured);
params.enableGroup(false);
// We don't want a basic chat room
params.setBackend(ChatRoomBackend.FlexisipChat);
Address[] participants = new Address[1];
participants[0] = participant;
mChatRoom =
core.createChatRoom(
params, getString(R.string.dummy_group_chat_subject), participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[History Detail Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
if (defaultProxyConfig != null) {
ChatRoom room =
core.findOneToOneChatRoom(
defaultProxyConfig.getContact(), participant, isSecured);
if (room != null) {
((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
} else {
room = core.getChatRoom(participant);
if (room != null) {
((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
if (defaultProxyConfig.getConferenceFactoryUri() != null
&& (isSecured
|| !LinphonePreferences.instance().useBasicChatRoomFor1To1())) {
mWaitLayout.setVisibility(View.VISIBLE);
ChatRoomParams params = core.createDefaultChatRoomParams();
params.enableEncryption(isSecured);
params.enableGroup(false);
// We don't want a basic chat room,
// so if isSecured is false we have to set this manually
params.setBackend(ChatRoomBackend.FlexisipChat);
Address[] participants = new Address[1];
participants[0] = participant;
mChatRoom =
core.createChatRoom(
params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[History Detail Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
} else {
room = core.getChatRoom(participant);
if (room != null) {
((HistoryActivity) getActivity())
.showChatRoom(room.getLocalAddress(), room.getPeerAddress());
}
}
}
} 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.Arrays;
import java.util.List;
import org.linphone.LinphoneContext;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.call.views.LinphoneLinearLayoutManager;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.utils.SelectableHelper;
import org.linphone.views.LinphoneLinearLayoutManager;
public class HistoryFragment extends Fragment
implements OnClickListener,
OnItemClickListener,
HistoryViewHolder.ClickListener,
ContactsUpdatedListener,
SelectableHelper.DeleteListener {
SelectableHelper.DeleteListener,
LinphoneContext.CoreStartedListener {
private RecyclerView mHistoryList;
private TextView mNoCallHistory, mNoMissedCallHistory;
private ImageView mMissedCalls, mAllCalls;
@ -60,6 +63,7 @@ public class HistoryFragment extends Fragment
private List<CallLog> mLogs;
private HistoryAdapter mHistoryAdapter;
private SelectableHelper mSelectionHelper;
private CoreListenerStub mListener;
@Override
public View onCreateView(
@ -94,26 +98,37 @@ public class HistoryFragment extends Fragment
mAllCalls.setEnabled(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;
}
@Override
public void onResume() {
super.onResume();
ContactsManager.getInstance().addContactsListener(this);
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);
ContactsManager.getInstance().addContactsListener(this);
LinphoneContext.instance().addCoreStartedListener(this);
LinphoneManager.getCore().addListener(mListener);
reloadData();
}
@Override
public void onPause() {
ContactsManager.getInstance().removeContactsListener(this);
LinphoneContext.instance().removeCoreStartedListener(this);
LinphoneManager.getCore().removeListener(mListener);
super.onPause();
}
@ -125,6 +140,11 @@ public class HistoryFragment extends Fragment
}
}
@Override
public void onCoreStarted() {
reloadData();
}
@Override
public void onClick(View v) {
int id = v.getId();
@ -177,15 +197,18 @@ public class HistoryFragment extends Fragment
if (mHistoryAdapter.isEditionEnabled()) {
mHistoryAdapter.toggleSelection(position);
} else {
CallLog log = mLogs.get(position);
Address address;
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
} else {
address = log.getToAddress();
}
if (address != null) {
LinphoneManager.getCallManager().newOutgoingCall(address.asStringUriOnly(), null);
if (position >= 0 && position < mLogs.size()) {
CallLog log = mLogs.get(position);
Address address;
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
} else {
address = log.getToAddress();
}
if (address != 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() {
if (mOnlyDisplayMissedCalls) {
List<CallLog> missedCalls = new ArrayList<>();

View file

@ -29,12 +29,11 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.service.notification.StatusBarNotification;
import java.io.File;
import java.util.HashMap;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.activities.DialerActivity;
import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity;
import org.linphone.call.CallOutgoingActivity;
@ -53,7 +52,9 @@ import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Reason;
import org.linphone.core.tools.Log;
import org.linphone.dialer.DialerActivity;
import org.linphone.history.HistoryActivity;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.DeviceUtils;
import org.linphone.utils.FileUtils;
@ -84,7 +85,19 @@ public class NotificationsManager {
mCurrentChatRoomAddress = null;
mNM = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
mNM.cancelAll();
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();
}
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) {
Log.i("[Notifications Manager] Notifying " + id);
mNM.notify(id, notif);
@ -521,7 +538,8 @@ public class NotificationsManager {
mContext,
mContext.getString(R.string.missed_calls_notif_title),
body,
pendingIntent);
pendingIntent,
missedCallCount);
sendNotification(MISSED_CALLS_NOTIF_ID, notif);
}

View file

@ -19,6 +19,7 @@
*/
package org.linphone.receivers;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -28,10 +29,40 @@ import org.linphone.LinphoneManager;
import org.linphone.core.tools.Log;
public class BluetoothReceiver extends BroadcastReceiver {
public BluetoothReceiver() {
super();
Log.i("[Bluetooth] Bluetooth receiver created");
}
@Override
public void onReceive(Context context, Intent intent) {
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 =
intent.getIntExtra(
BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
@ -55,6 +86,8 @@ public class BluetoothReceiver extends BroadcastReceiver {
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
Log.i("[Bluetooth] Bluetooth headset audio disconnected");
LinphoneManager.getAudioManager().bluetoothHeadetAudioConnectionChanged(false);
} else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
Log.i("[Bluetooth] Bluetooth headset audio connecting");
} else {
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.Context;
import android.content.Intent;
import org.linphone.LinphoneService;
import org.linphone.compatibility.Compatibility;
import org.linphone.service.LinphoneService;
import org.linphone.settings.LinphonePreferences;
public class BootReceiver extends BroadcastReceiver {
@ -36,17 +36,34 @@ public class BootReceiver extends BroadcastReceiver {
"[Boot Receiver] Device is shutting down, destroying Core to unregister");
context.stopService(
new Intent(Intent.ACTION_MAIN).setClass(context, LinphoneService.class));
} else {
} else if (intent.getAction().equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
LinphonePreferences.instance().setContext(context);
boolean autostart = LinphonePreferences.instance().isAutoStartEnabled();
android.util.Log.i(
"Linphone", "[Boot Receiver] Device is starting, auto_start is " + autostart);
if (autostart && !LinphoneService.isReady()) {
Intent serviceIntent = new Intent(Intent.ACTION_MAIN);
serviceIntent.setClass(context, LinphoneService.class);
serviceIntent.putExtra("ForceStartForeground", true);
Compatibility.startService(context, serviceIntent);
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);
serviceIntent.setClass(context, LinphoneService.class);
serviceIntent.putExtra("ForceStartForeground", true);
Compatibility.startService(context, serviceIntent);
}
}

View file

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

View file

@ -17,15 +17,15 @@
* 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.utils;
package org.linphone.service;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import java.util.ArrayList;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
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

View file

@ -17,22 +17,24 @@
* 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;
package org.linphone.service;
import android.app.Application;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
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.Core;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
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 {
private static LinphoneService sInstance;
@ -75,8 +77,12 @@ public final class LinphoneService extends Service {
}
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");
// We need to call this asap after the Service can be accessed through it's singleton
LinphoneContext.instance().getNotificationManager().startForeground();
}
@ -86,14 +92,7 @@ public final class LinphoneService extends Service {
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");
return START_STICKY;
}

View file

@ -17,9 +17,9 @@
* 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.utils;
package org.linphone.service;
import org.linphone.LinphoneService;
import org.linphone.utils.LinphoneUtils;
public class ServiceWaitThread extends Thread {
private ServiceWaitThreadListener mListener;

View file

@ -17,7 +17,7 @@
* 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.utils;
package org.linphone.service;
public interface ServiceWaitThreadListener {
void onServiceReady();

View file

@ -240,6 +240,9 @@ public class AccountSettingsFragment extends SettingsFragment {
if (mAuthInfo != null) {
mAuthInfo.setHa1(null);
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();
if (core != null) {
core.addAuthInfo(mAuthInfo);

View file

@ -83,6 +83,8 @@ public class AdvancedSettingsFragment extends SettingsFragment {
mStartAtBoot = mRootView.findViewById(R.id.pref_autostart);
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.setInputType(

View file

@ -20,9 +20,12 @@
package org.linphone.settings;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
@ -54,7 +57,7 @@ public class CallSettingsFragment extends SettingsFragment {
mAutoAnswer;
private ListSetting mMediaEncryption;
private TextSetting mAutoAnswerTime, mIncomingCallTimeout, mVoiceMailUri;
private BasicSetting mDndPermissionSettings;
private BasicSetting mDndPermissionSettings, mAndroidNotificationSettings;
@Nullable
@Override
@ -90,6 +93,8 @@ public class CallSettingsFragment extends SettingsFragment {
mMediaEncryption = mRootView.findViewById(R.id.pref_media_encryption);
initMediaEncryptionList();
mAndroidNotificationSettings = mRootView.findViewById(R.id.pref_android_app_notif_settings);
mAutoAnswerTime = mRootView.findViewById(R.id.pref_auto_answer_time);
mAutoAnswerTime.setInputType(InputType.TYPE_CLASS_NUMBER);
@ -167,8 +172,15 @@ public class CallSettingsFragment extends SettingsFragment {
@Override
public void onListValueChanged(int position, String newLabel, String newValue) {
try {
mPrefs.setMediaEncryption(
MediaEncryption.fromInt(Integer.parseInt(newValue)));
MediaEncryption encryption =
MediaEncryption.fromInt(Integer.parseInt(newValue));
mPrefs.setMediaEncryption(encryption);
if (encryption == MediaEncryption.None) {
mMediaEncryptionMandatory.setChecked(false);
}
mMediaEncryptionMandatory.setEnabled(
encryption != MediaEncryption.None);
} catch (NumberFormatException nfe) {
Log.e(nfe);
}
@ -223,6 +235,27 @@ public class CallSettingsFragment extends SettingsFragment {
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() {
@ -248,7 +281,12 @@ public class CallSettingsFragment extends SettingsFragment {
mDndPermissionSettings.setVisibility(
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();
}

View file

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

View file

@ -312,6 +312,7 @@ public class VideoSettingsFragment extends SettingsFragment {
: View.GONE);
mAutoInitiate.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);
mBandwidth.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);
mVideoCodecs.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() {

View file

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

View file

@ -22,6 +22,7 @@ package org.linphone.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@ -55,7 +56,7 @@ public class ImageUtils {
return bm;
}
private static Bitmap getRoundBitmap(Bitmap bitmap) {
public static Bitmap getRoundBitmap(Bitmap bitmap) {
Bitmap output =
Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
@ -85,4 +86,14 @@ public class ImageUtils {
/ ((float) context.getResources().getDisplayMetrics().densityDpi
/ 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:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:gravity="bottom">
<!-- This is a better way of splitting screen 50/50 than using weights -->
@ -119,29 +120,30 @@
android:layout_centerHorizontal="true" />
<include layout="@layout/call_primary_buttons"
android:id="@+id/call_primary_buttons"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
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"
android:id="@+id/call_secondary_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/vertical_divider" />
</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

View file

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

View file

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

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