diff --git a/.tx/config b/.tx/config index 35511f253..93225826f 100644 --- a/.tx/config +++ b/.tx/config @@ -8,3 +8,4 @@ type = ANDROID file_filter = app/src/main/res/values-/strings.xml source_file = app/src/main/res/values/strings.xml source_lang = en + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dcac501a..83ca3e742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index a682c7e86..cd8579f4f 100644 --- a/README.md +++ b/README.md @@ -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 @@ -69,7 +97,7 @@ Now, simply edit the app/build.gradle file and change the value returned by meth The next build will automatically use this value everywhere thanks to ```manifestPlaceholders``` feature of gradle and Android. You may have already noticed that the app installed by Android Studio has ```org.linphone.debug``` package name. -If you build the app as release, the package name will be ```org.linphone```. +If you build the app as release, the package name will be ```org.linphone```. ## Firebase push notifications @@ -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. +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. \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d0494b6a4..9c043d493 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ef5e73bb9..403bcdf2b 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> @@ -81,7 +82,7 @@ @@ -211,7 +212,7 @@ - - + diff --git a/app/src/main/java/org/linphone/LinphoneContext.java b/app/src/main/java/org/linphone/LinphoneContext.java index 0db32d971..30442ac61 100644 --- a/app/src/main/java/org/linphone/LinphoneContext.java +++ b/app/src/main/java/org/linphone/LinphoneContext.java @@ -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 mIncomingReceivedActivity = CallIncomingActivity.class; + private final ArrayList 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) 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(); + } } diff --git a/app/src/main/java/org/linphone/LinphoneManager.java b/app/src/main/java/org/linphone/LinphoneManager.java index ea6e900df..624f6aa1b 100644 --- a/app/src/main/java/org/linphone/LinphoneManager.java +++ b/app/src/main/java/org/linphone/LinphoneManager.java @@ -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(); } } diff --git a/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java b/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java index c55bdc444..100fe0c41 100644 --- a/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java +++ b/app/src/main/java/org/linphone/activities/LinphoneGenericActivity.java @@ -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)); } } } diff --git a/app/src/main/java/org/linphone/activities/LinphoneLauncherActivity.java b/app/src/main/java/org/linphone/activities/LinphoneLauncherActivity.java index 6becbc6a6..dda3c0cef 100644 --- a/app/src/main/java/org/linphone/activities/LinphoneLauncherActivity.java +++ b/app/src/main/java/org/linphone/activities/LinphoneLauncherActivity.java @@ -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 { diff --git a/app/src/main/java/org/linphone/activities/MainActivity.java b/app/src/main/java/org/linphone/activities/MainActivity.java index e8e83b83c..ca509dd24 100644 --- a/app/src/main/java/org/linphone/activities/MainActivity.java +++ b/app/src/main/java/org/linphone/activities/MainActivity.java @@ -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"); diff --git a/app/src/main/java/org/linphone/assistant/AccountConnectionAssistantActivity.java b/app/src/main/java/org/linphone/assistant/AccountConnectionAssistantActivity.java index 635994b82..32fa92154 100644 --- a/app/src/main/java/org/linphone/assistant/AccountConnectionAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/AccountConnectionAssistantActivity.java @@ -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 diff --git a/app/src/main/java/org/linphone/assistant/AssistantActivity.java b/app/src/main/java/org/linphone/assistant/AssistantActivity.java index 2edc702b5..a18883829 100644 --- a/app/src/main/java/org/linphone/assistant/AssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/AssistantActivity.java @@ -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) { diff --git a/app/src/main/java/org/linphone/assistant/EmailAccountCreationAssistantActivity.java b/app/src/main/java/org/linphone/assistant/EmailAccountCreationAssistantActivity.java index 36812b18a..91f5b4649 100644 --- a/app/src/main/java/org/linphone/assistant/EmailAccountCreationAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/EmailAccountCreationAssistantActivity.java @@ -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); } } diff --git a/app/src/main/java/org/linphone/assistant/EmailAccountValidationAssistantActivity.java b/app/src/main/java/org/linphone/assistant/EmailAccountValidationAssistantActivity.java index 40c6c342f..7d38e85d7 100644 --- a/app/src/main/java/org/linphone/assistant/EmailAccountValidationAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/EmailAccountValidationAssistantActivity.java @@ -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); } } diff --git a/app/src/main/java/org/linphone/assistant/GenericConnectionAssistantActivity.java b/app/src/main/java/org/linphone/assistant/GenericConnectionAssistantActivity.java index 3dd5b8247..9ecc21b68 100644 --- a/app/src/main/java/org/linphone/assistant/GenericConnectionAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/GenericConnectionAssistantActivity.java @@ -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 diff --git a/app/src/main/java/org/linphone/assistant/OpenH264DownloadAssistantActivity.java b/app/src/main/java/org/linphone/assistant/OpenH264DownloadAssistantActivity.java index cc564fb7a..b2b38ecc5 100644 --- a/app/src/main/java/org/linphone/assistant/OpenH264DownloadAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/OpenH264DownloadAssistantActivity.java @@ -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); } diff --git a/app/src/main/java/org/linphone/assistant/PhoneAccountCreationAssistantActivity.java b/app/src/main/java/org/linphone/assistant/PhoneAccountCreationAssistantActivity.java index fb66e5c45..90b600672 100644 --- a/app/src/main/java/org/linphone/assistant/PhoneAccountCreationAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/PhoneAccountCreationAssistantActivity.java @@ -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) { diff --git a/app/src/main/java/org/linphone/assistant/PhoneAccountLinkingAssistantActivity.java b/app/src/main/java/org/linphone/assistant/PhoneAccountLinkingAssistantActivity.java index 95edb6a52..c768a346f 100644 --- a/app/src/main/java/org/linphone/assistant/PhoneAccountLinkingAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/PhoneAccountLinkingAssistantActivity.java @@ -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 diff --git a/app/src/main/java/org/linphone/assistant/PhoneAccountValidationAssistantActivity.java b/app/src/main/java/org/linphone/assistant/PhoneAccountValidationAssistantActivity.java index dacf89144..857efd36c 100644 --- a/app/src/main/java/org/linphone/assistant/PhoneAccountValidationAssistantActivity.java +++ b/app/src/main/java/org/linphone/assistant/PhoneAccountValidationAssistantActivity.java @@ -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 + } } } diff --git a/app/src/main/java/org/linphone/utils/AndroidAudioManager.java b/app/src/main/java/org/linphone/call/AndroidAudioManager.java similarity index 80% rename from app/src/main/java/org/linphone/utils/AndroidAudioManager.java rename to app/src/main/java/org/linphone/call/AndroidAudioManager.java index dc60a0367..0bc609fe4 100644 --- a/app/src/main/java/org/linphone/utils/AndroidAudioManager.java +++ b/app/src/main/java/org/linphone/call/AndroidAudioManager.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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 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 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(); } } diff --git a/app/src/main/java/org/linphone/call/CallActivity.java b/app/src/main/java/org/linphone/call/CallActivity.java index 5bd4e77d0..c3fdf5b65 100644 --- a/app/src/main/java/org/linphone/call/CallActivity.java +++ b/app/src/main/java/org/linphone/call/CallActivity.java @@ -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 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 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; + } } } diff --git a/app/src/main/java/org/linphone/call/CallIncomingActivity.java b/app/src/main/java/org/linphone/call/CallIncomingActivity.java index 205c22f47..a21d50187 100644 --- a/app/src/main/java/org/linphone/call/CallIncomingActivity.java +++ b/app/src/main/java/org/linphone/call/CallIncomingActivity.java @@ -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); diff --git a/app/src/main/java/org/linphone/call/CallManager.java b/app/src/main/java/org/linphone/call/CallManager.java index aa4d778b3..87c06a9f9 100644 --- a/app/src/main/java/org/linphone/call/CallManager.java +++ b/app/src/main/java/org/linphone/call/CallManager.java @@ -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) { diff --git a/app/src/main/java/org/linphone/call/CallOutgoingActivity.java b/app/src/main/java/org/linphone/call/CallOutgoingActivity.java index 372019192..d538de46d 100644 --- a/app/src/main/java/org/linphone/call/CallOutgoingActivity.java +++ b/app/src/main/java/org/linphone/call/CallOutgoingActivity.java @@ -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); diff --git a/app/src/main/java/org/linphone/call/CallStatsAdapter.java b/app/src/main/java/org/linphone/call/CallStatsAdapter.java index 4e9223215..f963b0b75 100644 --- a/app/src/main/java/org/linphone/call/CallStatsAdapter.java +++ b/app/src/main/java/org/linphone/call/CallStatsAdapter.java @@ -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; diff --git a/app/src/main/java/org/linphone/call/CallStatsFragment.java b/app/src/main/java/org/linphone/call/CallStatsFragment.java index d088baf71..504a99e87 100644 --- a/app/src/main/java/org/linphone/call/CallStatsFragment.java +++ b/app/src/main/java/org/linphone/call/CallStatsFragment.java @@ -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())); } diff --git a/app/src/main/java/org/linphone/call/CallStatusBarFragment.java b/app/src/main/java/org/linphone/call/CallStatusBarFragment.java index 3ac6b6acf..16e6180b5 100644 --- a/app/src/main/java/org/linphone/call/CallStatusBarFragment.java +++ b/app/src/main/java/org/linphone/call/CallStatusBarFragment.java @@ -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()) diff --git a/app/src/main/java/org/linphone/views/CallButton.java b/app/src/main/java/org/linphone/call/views/CallButton.java similarity index 96% rename from app/src/main/java/org/linphone/views/CallButton.java rename to app/src/main/java/org/linphone/call/views/CallButton.java index 655577739..347289b2d 100644 --- a/app/src/main/java/org/linphone/views/CallButton.java +++ b/app/src/main/java/org/linphone/call/views/CallButton.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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") diff --git a/app/src/main/java/org/linphone/views/CallIncomingAnswerButton.java b/app/src/main/java/org/linphone/call/views/CallIncomingAnswerButton.java similarity index 99% rename from app/src/main/java/org/linphone/views/CallIncomingAnswerButton.java rename to app/src/main/java/org/linphone/call/views/CallIncomingAnswerButton.java index d762d2c71..9645ac45d 100644 --- a/app/src/main/java/org/linphone/views/CallIncomingAnswerButton.java +++ b/app/src/main/java/org/linphone/call/views/CallIncomingAnswerButton.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.content.Context; import android.util.AttributeSet; diff --git a/app/src/main/java/org/linphone/views/CallIncomingButtonListener.java b/app/src/main/java/org/linphone/call/views/CallIncomingButtonListener.java similarity index 96% rename from app/src/main/java/org/linphone/views/CallIncomingButtonListener.java rename to app/src/main/java/org/linphone/call/views/CallIncomingButtonListener.java index 1ec9752de..9127431ec 100644 --- a/app/src/main/java/org/linphone/views/CallIncomingButtonListener.java +++ b/app/src/main/java/org/linphone/call/views/CallIncomingButtonListener.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; public interface CallIncomingButtonListener { void onAction(); diff --git a/app/src/main/java/org/linphone/views/CallIncomingDeclineButton.java b/app/src/main/java/org/linphone/call/views/CallIncomingDeclineButton.java similarity index 99% rename from app/src/main/java/org/linphone/views/CallIncomingDeclineButton.java rename to app/src/main/java/org/linphone/call/views/CallIncomingDeclineButton.java index ddcee0294..65b422c34 100644 --- a/app/src/main/java/org/linphone/views/CallIncomingDeclineButton.java +++ b/app/src/main/java/org/linphone/call/views/CallIncomingDeclineButton.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.content.Context; import android.util.AttributeSet; diff --git a/app/src/main/java/org/linphone/views/LinphoneGL2JNIViewOverlay.java b/app/src/main/java/org/linphone/call/views/LinphoneGL2JNIViewOverlay.java similarity index 99% rename from app/src/main/java/org/linphone/views/LinphoneGL2JNIViewOverlay.java rename to app/src/main/java/org/linphone/call/views/LinphoneGL2JNIViewOverlay.java index 210462340..b18e7165a 100644 --- a/app/src/main/java/org/linphone/views/LinphoneGL2JNIViewOverlay.java +++ b/app/src/main/java/org/linphone/call/views/LinphoneGL2JNIViewOverlay.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.content.Context; import android.content.Intent; diff --git a/app/src/main/java/org/linphone/views/LinphoneLinearLayoutManager.java b/app/src/main/java/org/linphone/call/views/LinphoneLinearLayoutManager.java similarity index 98% rename from app/src/main/java/org/linphone/views/LinphoneLinearLayoutManager.java rename to app/src/main/java/org/linphone/call/views/LinphoneLinearLayoutManager.java index a0d32a9cd..4694ac63d 100644 --- a/app/src/main/java/org/linphone/views/LinphoneLinearLayoutManager.java +++ b/app/src/main/java/org/linphone/call/views/LinphoneLinearLayoutManager.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.content.Context; import android.util.AttributeSet; diff --git a/app/src/main/java/org/linphone/views/LinphoneOverlay.java b/app/src/main/java/org/linphone/call/views/LinphoneOverlay.java similarity index 97% rename from app/src/main/java/org/linphone/views/LinphoneOverlay.java rename to app/src/main/java/org/linphone/call/views/LinphoneOverlay.java index e1cc70ec2..0172957d1 100644 --- a/app/src/main/java/org/linphone/views/LinphoneOverlay.java +++ b/app/src/main/java/org/linphone/call/views/LinphoneOverlay.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.view.WindowManager; diff --git a/app/src/main/java/org/linphone/views/LinphoneTextureViewOverlay.java b/app/src/main/java/org/linphone/call/views/LinphoneTextureViewOverlay.java similarity index 99% rename from app/src/main/java/org/linphone/views/LinphoneTextureViewOverlay.java rename to app/src/main/java/org/linphone/call/views/LinphoneTextureViewOverlay.java index aa010d457..c5b30ab0d 100644 --- a/app/src/main/java/org/linphone/views/LinphoneTextureViewOverlay.java +++ b/app/src/main/java/org/linphone/call/views/LinphoneTextureViewOverlay.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.call.views; import android.content.Context; import android.content.Intent; diff --git a/app/src/main/java/org/linphone/chat/ChatActivity.java b/app/src/main/java/org/linphone/chat/ChatActivity.java index 2db78e9b9..0b72c846c 100644 --- a/app/src/main/java/org/linphone/chat/ChatActivity.java +++ b/app/src/main/java/org/linphone/chat/ChatActivity.java @@ -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 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()); diff --git a/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java b/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java index 61fe7c994..5c59fa274 100644 --- a/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java +++ b/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java @@ -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(); + } } } }); diff --git a/app/src/main/java/org/linphone/chat/ChatMessagesAdapter.java b/app/src/main/java/org/linphone/chat/ChatMessagesAdapter.java index 7fc4e7718..c4d5ff848 100644 --- a/app/src/main/java/org/linphone/chat/ChatMessagesAdapter.java +++ b/app/src/main/java/org/linphone/chat/ChatMessagesAdapter.java @@ -119,6 +119,7 @@ public class ChatMessagesAdapter extends SelectableAdapter 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(); diff --git a/app/src/main/java/org/linphone/chat/ChatRoomViewHolder.java b/app/src/main/java/org/linphone/chat/ChatRoomViewHolder.java index 6ffb1a893..0d0693542 100644 --- a/app/src/main/java/org/linphone/chat/ChatRoomViewHolder.java +++ b/app/src/main/java/org/linphone/chat/ChatRoomViewHolder.java @@ -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 { diff --git a/app/src/main/java/org/linphone/chat/ChatRoomsFragment.java b/app/src/main/java/org/linphone/chat/ChatRoomsFragment.java index e8fee018c..b463c9bd8 100644 --- a/app/src/main/java/org/linphone/chat/ChatRoomsFragment.java +++ b/app/src/main/java/org/linphone/chat/ChatRoomsFragment.java @@ -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) { diff --git a/app/src/main/java/org/linphone/chat/DevicesAdapter.java b/app/src/main/java/org/linphone/chat/DevicesAdapter.java index c2e4f8cbc..57ee8eb86 100644 --- a/app/src/main/java/org/linphone/chat/DevicesAdapter.java +++ b/app/src/main/java/org/linphone/chat/DevicesAdapter.java @@ -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; diff --git a/app/src/main/java/org/linphone/chat/DevicesFragment.java b/app/src/main/java/org/linphone/chat/DevicesFragment.java index 35ad77cd6..72d3f1d18 100644 --- a/app/src/main/java/org/linphone/chat/DevicesFragment.java +++ b/app/src/main/java/org/linphone/chat/DevicesFragment.java @@ -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()); diff --git a/app/src/main/java/org/linphone/chat/GroupInfoAdapter.java b/app/src/main/java/org/linphone/chat/GroupInfoAdapter.java index 285e104f6..efbed24b5 100644 --- a/app/src/main/java/org/linphone/chat/GroupInfoAdapter.java +++ b/app/src/main/java/org/linphone/chat/GroupInfoAdapter.java @@ -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 { private List mItems; diff --git a/app/src/main/java/org/linphone/chat/GroupInfoFragment.java b/app/src/main/java/org/linphone/chat/GroupInfoFragment.java index 0b9d27741..331200c2d 100644 --- a/app/src/main/java/org/linphone/chat/GroupInfoFragment.java +++ b/app/src/main/java/org/linphone/chat/GroupInfoFragment.java @@ -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(); } diff --git a/app/src/main/java/org/linphone/chat/ImdnFragment.java b/app/src/main/java/org/linphone/chat/ImdnFragment.java index 6daf78a69..149a6a96d 100644 --- a/app/src/main/java/org/linphone/chat/ImdnFragment.java +++ b/app/src/main/java/org/linphone/chat/ImdnFragment.java @@ -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; diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentyFourPlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentyFourPlus.java index 2a2720be3..54a3812d4 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentyFourPlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentyFourPlus.java @@ -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) diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentyOnePlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentyOnePlus.java index 47faa1a2b..761830adb 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentyOnePlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentyOnePlus.java @@ -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(); } diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java index 4474cd15e..03aa2c732 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentySixPlus.java @@ -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(); } diff --git a/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java b/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java index fd81bae66..cb0c0d24b 100644 --- a/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java +++ b/app/src/main/java/org/linphone/compatibility/ApiTwentyThreePlus.java @@ -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(); + } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.java b/app/src/main/java/org/linphone/compatibility/Compatibility.java index ad2b2af26..057c6e68a 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.java +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.java @@ -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]; + } } diff --git a/app/src/main/java/org/linphone/contacts/AndroidContact.java b/app/src/main/java/org/linphone/contacts/AndroidContact.java index 4953d8f95..fba283b2c 100644 --- a/app/src/main/java/org/linphone/contacts/AndroidContact.java +++ b/app/src/main/java/org/linphone/contacts/AndroidContact.java @@ -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 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); + } } } diff --git a/app/src/main/java/org/linphone/contacts/AsyncContactsLoader.java b/app/src/main/java/org/linphone/contacts/AsyncContactsLoader.java index fe4bbf37e..c4941371a 100644 --- a/app/src/main/java/org/linphone/contacts/AsyncContactsLoader.java +++ b/app/src/main/java/org/linphone/contacts/AsyncContactsLoader.java @@ -79,21 +79,6 @@ class AsyncContactsLoader extends AsyncTask androidContactsCache = new HashMap<>(); AsyncContactsData data = new AsyncContactsData(); List nativeIds = new ArrayList<>(); @@ -111,10 +96,12 @@ class AsyncContactsLoader extends AsyncTask 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"); diff --git a/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java b/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java index 144fe408e..04f253a0a 100644 --- a/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java +++ b/app/src/main/java/org/linphone/contacts/ContactDetailsFragment.java @@ -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()); + } } } } diff --git a/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java b/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java index 63d798460..238681f51 100644 --- a/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java +++ b/app/src/main/java/org/linphone/contacts/ContactEditorFragment.java @@ -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 cameraIntents = new ArrayList<>(); - final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + List 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) { diff --git a/app/src/main/java/org/linphone/contacts/ContactsActivity.java b/app/src/main/java/org/linphone/contacts/ContactsActivity.java index a4236d1d4..5559e19c8 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsActivity.java +++ b/app/src/main/java/org/linphone/contacts/ContactsActivity.java @@ -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); diff --git a/app/src/main/java/org/linphone/contacts/ContactsAdapter.java b/app/src/main/java/org/linphone/contacts/ContactsAdapter.java index fc3a31684..af7f7d5b8 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsAdapter.java +++ b/app/src/main/java/org/linphone/contacts/ContactsAdapter.java @@ -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 implements SectionIndexer { diff --git a/app/src/main/java/org/linphone/contacts/ContactsFragment.java b/app/src/main/java/org/linphone/contacts/ContactsFragment.java index d5bfd0183..0d6028d75 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsFragment.java +++ b/app/src/main/java/org/linphone/contacts/ContactsFragment.java @@ -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, diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.java b/app/src/main/java/org/linphone/contacts/ContactsManager.java index 82f9c890e..1cedbcf05 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.java +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.java @@ -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 mContacts, mSipContacts; private final ArrayList 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 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 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) diff --git a/app/src/main/java/org/linphone/contacts/LinphoneContact.java b/app/src/main/java/org/linphone/contacts/LinphoneContact.java index 756d71c58..23e257bcc 100644 --- a/app/src/main/java/org/linphone/contacts/LinphoneContact.java +++ b/app/src/main/java/org/linphone/contacts/LinphoneContact.java @@ -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(); } diff --git a/app/src/main/java/org/linphone/contacts/SearchContactViewHolder.java b/app/src/main/java/org/linphone/contacts/SearchContactViewHolder.java index 89a47050e..8085860e6 100644 --- a/app/src/main/java/org/linphone/contacts/SearchContactViewHolder.java +++ b/app/src/main/java/org/linphone/contacts/SearchContactViewHolder.java @@ -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()); + } } } diff --git a/app/src/main/java/org/linphone/contacts/SearchContactsAdapter.java b/app/src/main/java/org/linphone/contacts/SearchContactsAdapter.java index 8c421c8cb..c0e67e03e 100644 --- a/app/src/main/java/org/linphone/contacts/SearchContactsAdapter.java +++ b/app/src/main/java/org/linphone/contacts/SearchContactsAdapter.java @@ -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 { private List mContacts; @@ -118,8 +119,20 @@ public class SearchContactsAdapter extends RecyclerView.Adapter. */ -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); } diff --git a/app/src/main/java/org/linphone/contacts/views/ContactAvatarHolder.java b/app/src/main/java/org/linphone/contacts/views/ContactAvatarHolder.java new file mode 100644 index 000000000..3ae8bce5c --- /dev/null +++ b/app/src/main/java/org/linphone/contacts/views/ContactAvatarHolder.java @@ -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 . + */ +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); + } +} diff --git a/app/src/main/java/org/linphone/views/ContactSelectView.java b/app/src/main/java/org/linphone/contacts/views/ContactSelectView.java similarity index 98% rename from app/src/main/java/org/linphone/views/ContactSelectView.java rename to app/src/main/java/org/linphone/contacts/views/ContactSelectView.java index 3507fb18b..60ba826d2 100644 --- a/app/src/main/java/org/linphone/views/ContactSelectView.java +++ b/app/src/main/java/org/linphone/contacts/views/ContactSelectView.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.contacts.views; import android.content.Context; import android.view.LayoutInflater; diff --git a/app/src/main/java/org/linphone/activities/DialerActivity.java b/app/src/main/java/org/linphone/dialer/DialerActivity.java similarity index 95% rename from app/src/main/java/org/linphone/activities/DialerActivity.java rename to app/src/main/java/org/linphone/dialer/DialerActivity.java index ccec6c105..6e1e3a534 100644 --- a/app/src/main/java/org/linphone/activities/DialerActivity.java +++ b/app/src/main/java/org/linphone/dialer/DialerActivity.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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); diff --git a/app/src/main/java/org/linphone/views/AddressAware.java b/app/src/main/java/org/linphone/dialer/views/AddressAware.java similarity index 96% rename from app/src/main/java/org/linphone/views/AddressAware.java rename to app/src/main/java/org/linphone/dialer/views/AddressAware.java index f43ba4af1..61aa2a63e 100644 --- a/app/src/main/java/org/linphone/views/AddressAware.java +++ b/app/src/main/java/org/linphone/dialer/views/AddressAware.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.dialer.views; public interface AddressAware { void setAddressWidget(AddressText address); diff --git a/app/src/main/java/org/linphone/views/AddressText.java b/app/src/main/java/org/linphone/dialer/views/AddressText.java similarity index 99% rename from app/src/main/java/org/linphone/views/AddressText.java rename to app/src/main/java/org/linphone/dialer/views/AddressText.java index 8cdce79ff..ea199cafc 100644 --- a/app/src/main/java/org/linphone/views/AddressText.java +++ b/app/src/main/java/org/linphone/dialer/views/AddressText.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.dialer.views; import android.annotation.SuppressLint; import android.content.Context; diff --git a/app/src/main/java/org/linphone/views/AddressType.java b/app/src/main/java/org/linphone/dialer/views/AddressType.java similarity index 96% rename from app/src/main/java/org/linphone/views/AddressType.java rename to app/src/main/java/org/linphone/dialer/views/AddressType.java index 9c15cd4c7..fd3088a4a 100644 --- a/app/src/main/java/org/linphone/views/AddressType.java +++ b/app/src/main/java/org/linphone/dialer/views/AddressType.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.dialer.views; public interface AddressType { CharSequence getText(); diff --git a/app/src/main/java/org/linphone/views/Digit.java b/app/src/main/java/org/linphone/dialer/views/Digit.java similarity index 99% rename from app/src/main/java/org/linphone/views/Digit.java rename to app/src/main/java/org/linphone/dialer/views/Digit.java index 409164ffd..951b71210 100644 --- a/app/src/main/java/org/linphone/views/Digit.java +++ b/app/src/main/java/org/linphone/dialer/views/Digit.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.dialer.views; import android.annotation.SuppressLint; import android.app.AlertDialog; diff --git a/app/src/main/java/org/linphone/views/EraseButton.java b/app/src/main/java/org/linphone/dialer/views/EraseButton.java similarity index 98% rename from app/src/main/java/org/linphone/views/EraseButton.java rename to app/src/main/java/org/linphone/dialer/views/EraseButton.java index a75688e34..0fc2645af 100644 --- a/app/src/main/java/org/linphone/views/EraseButton.java +++ b/app/src/main/java/org/linphone/dialer/views/EraseButton.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.views; +package org.linphone.dialer.views; import android.annotation.SuppressLint; import android.content.Context; diff --git a/app/src/main/java/org/linphone/firebase/FirebaseMessaging.java b/app/src/main/java/org/linphone/firebase/FirebaseMessaging.java index 123fc0b32..959fca4d7 100644 --- a/app/src/main/java/org/linphone/firebase/FirebaseMessaging.java +++ b/app/src/main/java/org/linphone/firebase/FirebaseMessaging.java @@ -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) { diff --git a/app/src/main/java/org/linphone/fragments/StatusBarFragment.java b/app/src/main/java/org/linphone/fragments/StatusBarFragment.java index 27f31d648..9c7e53bb6 100644 --- a/app/src/main/java/org/linphone/fragments/StatusBarFragment.java +++ b/app/src/main/java/org/linphone/fragments/StatusBarFragment.java @@ -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) { diff --git a/app/src/main/java/org/linphone/history/HistoryActivity.java b/app/src/main/java/org/linphone/history/HistoryActivity.java index 01399cca4..052550a38 100644 --- a/app/src/main/java/org/linphone/history/HistoryActivity.java +++ b/app/src/main/java/org/linphone/history/HistoryActivity.java @@ -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 diff --git a/app/src/main/java/org/linphone/history/HistoryAdapter.java b/app/src/main/java/org/linphone/history/HistoryAdapter.java index d5f105ec3..e61d3596d 100644 --- a/app/src/main/java/org/linphone/history/HistoryAdapter.java +++ b/app/src/main/java/org/linphone/history/HistoryAdapter.java @@ -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 { private final List mLogs; diff --git a/app/src/main/java/org/linphone/history/HistoryDetailFragment.java b/app/src/main/java/org/linphone/history/HistoryDetailFragment.java index c7e5753af..624adddac 100644 --- a/app/src/main/java/org/linphone/history/HistoryDetailFragment.java +++ b/app/src/main/java/org/linphone/history/HistoryDetailFragment.java @@ -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()); + } } } } diff --git a/app/src/main/java/org/linphone/history/HistoryFragment.java b/app/src/main/java/org/linphone/history/HistoryFragment.java index 6c86c3492..f2f150a09 100644 --- a/app/src/main/java/org/linphone/history/HistoryFragment.java +++ b/app/src/main/java/org/linphone/history/HistoryFragment.java @@ -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 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 missedCalls = new ArrayList<>(); diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.java b/app/src/main/java/org/linphone/notifications/NotificationsManager.java index 8cc5e8638..c16001487 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.java +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.java @@ -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); } diff --git a/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java b/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java index d2e677e1a..16cbe6b60 100644 --- a/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java +++ b/app/src/main/java/org/linphone/receivers/BluetoothReceiver.java @@ -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); } diff --git a/app/src/main/java/org/linphone/receivers/BootReceiver.java b/app/src/main/java/org/linphone/receivers/BootReceiver.java index 37a0966e6..a990d871b 100644 --- a/app/src/main/java/org/linphone/receivers/BootReceiver.java +++ b/app/src/main/java/org/linphone/receivers/BootReceiver.java @@ -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); + } } diff --git a/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java b/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java index cf59918a1..07cac4ff4 100644 --- a/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java +++ b/app/src/main/java/org/linphone/receivers/HeadsetReceiver.java @@ -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); } } } diff --git a/app/src/main/java/org/linphone/recording/RecordingsActivity.java b/app/src/main/java/org/linphone/recording/RecordingsActivity.java index 480e0e4c4..f1a270d0f 100644 --- a/app/src/main/java/org/linphone/recording/RecordingsActivity.java +++ b/app/src/main/java/org/linphone/recording/RecordingsActivity.java @@ -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 { diff --git a/app/src/main/java/org/linphone/utils/ActivityMonitor.java b/app/src/main/java/org/linphone/service/ActivityMonitor.java similarity index 98% rename from app/src/main/java/org/linphone/utils/ActivityMonitor.java rename to app/src/main/java/org/linphone/service/ActivityMonitor.java index 0a545a5d2..a5eef5e21 100644 --- a/app/src/main/java/org/linphone/utils/ActivityMonitor.java +++ b/app/src/main/java/org/linphone/service/ActivityMonitor.java @@ -17,15 +17,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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 diff --git a/app/src/main/java/org/linphone/LinphoneService.java b/app/src/main/java/org/linphone/service/LinphoneService.java similarity index 92% rename from app/src/main/java/org/linphone/LinphoneService.java rename to app/src/main/java/org/linphone/service/LinphoneService.java index 8920b4ad4..b08aa85b1 100644 --- a/app/src/main/java/org/linphone/LinphoneService.java +++ b/app/src/main/java/org/linphone/service/LinphoneService.java @@ -17,22 +17,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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; } diff --git a/app/src/main/java/org/linphone/utils/ServiceWaitThread.java b/app/src/main/java/org/linphone/service/ServiceWaitThread.java similarity index 95% rename from app/src/main/java/org/linphone/utils/ServiceWaitThread.java rename to app/src/main/java/org/linphone/service/ServiceWaitThread.java index 817607796..60e767e83 100644 --- a/app/src/main/java/org/linphone/utils/ServiceWaitThread.java +++ b/app/src/main/java/org/linphone/service/ServiceWaitThread.java @@ -17,9 +17,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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; diff --git a/app/src/main/java/org/linphone/utils/ServiceWaitThreadListener.java b/app/src/main/java/org/linphone/service/ServiceWaitThreadListener.java similarity index 96% rename from app/src/main/java/org/linphone/utils/ServiceWaitThreadListener.java rename to app/src/main/java/org/linphone/service/ServiceWaitThreadListener.java index 59a9762c4..3f4f19552 100644 --- a/app/src/main/java/org/linphone/utils/ServiceWaitThreadListener.java +++ b/app/src/main/java/org/linphone/service/ServiceWaitThreadListener.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.utils; +package org.linphone.service; public interface ServiceWaitThreadListener { void onServiceReady(); diff --git a/app/src/main/java/org/linphone/settings/AccountSettingsFragment.java b/app/src/main/java/org/linphone/settings/AccountSettingsFragment.java index 3499bf606..9be93c880 100644 --- a/app/src/main/java/org/linphone/settings/AccountSettingsFragment.java +++ b/app/src/main/java/org/linphone/settings/AccountSettingsFragment.java @@ -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); diff --git a/app/src/main/java/org/linphone/settings/AdvancedSettingsFragment.java b/app/src/main/java/org/linphone/settings/AdvancedSettingsFragment.java index f769af665..cc27bcb31 100644 --- a/app/src/main/java/org/linphone/settings/AdvancedSettingsFragment.java +++ b/app/src/main/java/org/linphone/settings/AdvancedSettingsFragment.java @@ -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( diff --git a/app/src/main/java/org/linphone/settings/CallSettingsFragment.java b/app/src/main/java/org/linphone/settings/CallSettingsFragment.java index eadeda704..597af0fc1 100644 --- a/app/src/main/java/org/linphone/settings/CallSettingsFragment.java +++ b/app/src/main/java/org/linphone/settings/CallSettingsFragment.java @@ -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(); } diff --git a/app/src/main/java/org/linphone/settings/LinphonePreferences.java b/app/src/main/java/org/linphone/settings/LinphonePreferences.java index d1d6aa11a..db442941d 100644 --- a/app/src/main/java/org/linphone/settings/LinphonePreferences.java +++ b/app/src/main/java/org/linphone/settings/LinphonePreferences.java @@ -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); } } diff --git a/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java b/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java index fd2662c93..fa520575e 100644 --- a/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java +++ b/app/src/main/java/org/linphone/settings/VideoSettingsFragment.java @@ -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() { diff --git a/app/src/main/java/org/linphone/utils/DeviceUtils.java b/app/src/main/java/org/linphone/utils/DeviceUtils.java index 4339e97bc..c5d271dd1 100644 --- a/app/src/main/java/org/linphone/utils/DeviceUtils.java +++ b/app/src/main/java/org/linphone/utils/DeviceUtils.java @@ -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(); } }); diff --git a/app/src/main/java/org/linphone/utils/ImageUtils.java b/app/src/main/java/org/linphone/utils/ImageUtils.java index d519b9b33..0053af9e1 100644 --- a/app/src/main/java/org/linphone/utils/ImageUtils.java +++ b/app/src/main/java/org/linphone/utils/ImageUtils.java @@ -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; + } } diff --git a/app/src/main/java/org/linphone/views/AsyncBitmap.java b/app/src/main/java/org/linphone/views/AsyncBitmap.java deleted file mode 100644 index 86ef3e676..000000000 --- a/app/src/main/java/org/linphone/views/AsyncBitmap.java +++ /dev/null @@ -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 . - */ -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 mBitmapWorkerTaskReference; - - public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - mBitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return mBitmapWorkerTaskReference.get(); - } -} diff --git a/app/src/main/java/org/linphone/views/BitmapWorkerTask.java b/app/src/main/java/org/linphone/views/BitmapWorkerTask.java deleted file mode 100644 index 66004db8f..000000000 --- a/app/src/main/java/org/linphone/views/BitmapWorkerTask.java +++ /dev/null @@ -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 . - */ -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 { - private String path; - - private final WeakReference 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(); - } - } - } - } -} diff --git a/app/src/main/res/drawable/call_numpad.xml b/app/src/main/res/drawable/call_numpad.xml new file mode 100644 index 000000000..340e506d4 --- /dev/null +++ b/app/src/main/res/drawable/call_numpad.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout-land/call.xml b/app/src/main/res/layout-land/call.xml index b47c08bba..e164b73f8 100644 --- a/app/src/main/res/layout-land/call.xml +++ b/app/src/main/res/layout-land/call.xml @@ -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"> @@ -119,29 +120,30 @@ android:layout_centerHorizontal="true" /> + + - - - - - - - - - - - - - - - - - + + - - - - + android:src="@drawable/call_numpad" /> diff --git a/app/src/main/res/layout/dialer.xml b/app/src/main/res/layout/dialer.xml index 43d6e7e82..cb2b325b5 100644 --- a/app/src/main/res/layout/dialer.xml +++ b/app/src/main/res/layout/dialer.xml @@ -10,7 +10,7 @@ android:layout_alignParentTop="true" android:background="?attr/lighToolbarBackgroundColor"> - - - - - - - - - - - - - - - - - + + diff --git a/app/src/main/res/raw/default_assistant_create.rc b/app/src/main/res/raw/default_assistant_create.rc index b96506737..489dfaa24 100644 --- a/app/src/main/res/raw/default_assistant_create.rc +++ b/app/src/main/res/raw/default_assistant_create.rc @@ -27,6 +27,7 @@
+ MD5 -1 0 diff --git a/app/src/main/res/raw/linphone_assistant_create.rc b/app/src/main/res/raw/linphone_assistant_create.rc index 0025e2f00..0c793b49e 100644 --- a/app/src/main/res/raw/linphone_assistant_create.rc +++ b/app/src/main/res/raw/linphone_assistant_create.rc @@ -27,6 +27,7 @@ https://lime.linphone.org/lime-server/lime-server.php
+ sip.linphone.org SHA-256 -1 1 diff --git a/app/src/main/res/raw/linphonerc_default b/app/src/main/res/raw/linphonerc_default index a220030d0..808feee28 100644 --- a/app/src/main/res/raw/linphonerc_default +++ b/app/src/main/res/raw/linphonerc_default @@ -8,6 +8,11 @@ sip_tcp_port=-1 sip_tls_port=-1 media_encryption=none +[net] +#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit" +download_bw=0 +upload_bw=0 + [video] size=vga diff --git a/app/src/main/res/raw/linphonerc_factory b/app/src/main/res/raw/linphonerc_factory index 85a96a979..a9a51cb5a 100644 --- a/app/src/main/res/raw/linphonerc_factory +++ b/app/src/main/res/raw/linphonerc_factory @@ -4,9 +4,6 @@ #Paths to resources must be set from LinphoneManager, after creating LinphoneCore. [net] mtu=1300 -#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit" -download_bw=0 -upload_bw=0 force_ice_disablement=0 [sip] diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4083d2aee..a0d4b1306 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -130,6 +130,7 @@ Vos amis pourront vous joindre plus facilement si vous associez votre compte à votre numéro de téléphone\n\nVous verrez dans votre carnet d\'adresses les contacts qui utilisent Linphone et vos amis sauront qu\'ils peuvent vous contacter.\n Vous ne pouvez associer votre numéro qu\'à un seul compte Linphone.\n\nSi vous avez déjà associé votre numéro à un autre compte mais préférez utiliser ce compte-ci, suivez la procédure d\'association et votre numéro sera automatiquement transféré à ce compte. Trop de SMS ont été envoyés vers ce numéro en un court laps de temps, veuillez attendre 24h avant de réessayer. + Ce compte n\'existe pas Mail non valide Ce compte existe déjà diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 998ef0bf5..8a06bdc9f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -243,6 +243,7 @@ Scarica Scegli una conversazione o creane una nuova Il trust è stato negato. Effettua una chiamata per avviare nuovamente la procedura di autenticazione. + Impossibile aprire il file, nessuna applicazione disponibile per questo formato. Connesso Non connesso @@ -286,7 +287,9 @@ Buffer jitter: Encoder: Decoder: + Filtro di riproduzione: Filtro di visualizzazione: + Filtro di acquisizione: Chiamata Vuoi cancellare il registro delle chiamate selezionate? @@ -366,6 +369,9 @@ Tunnel Nome host Porta + Abilita modalità doppia + Nome host (2° server per la modalità doppia) + Porta (2° server per la modalità doppia) Modalità @@ -394,6 +400,7 @@ Codec Video + Fotocamera Sovrapposizione video Mostra video di chiamata sul display quando ci si trova all\'esterno dell\'applicazione Usa la fotocamera frontale @@ -442,6 +449,8 @@ Mai Sempre Se minore della grandezza massima + Nascondi chat room vuote + Se mancano le chat room, prova a deselezionare questa impostazione Rete Usa solo Wi-Fi diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 47a1f996e..ee7cdd695 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -6,10 +6,20 @@ Linphone Linphone Bezig met opstarten + %s verbonden + %s verbinding kon niet tot stand gebracht worden Linphone Android %s Linphone Core %s + Linphone SDK %s + Lees onze privacy verklaring + linphone contacten + Antwoord + Verstuurd antwoord: %s + Verbinding verbreken + Beantwoorden + Aanduiden als gelezen Gebruikersnaam Telefoonnummer @@ -42,6 +52,7 @@ Nee prima Ja + Niet opnieuw tonen omschrijving @@ -54,6 +65,7 @@ geldig account_1 linphoen Transport + Uw SIP adres is Wachtwoord is te kort Wachtwoiord is te lang diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fedf91a19..16937ef3c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -129,6 +129,7 @@ \nВашим друзьям будет проще связаться с вами, если вы свяжете свой аккаунт со своим номером телефона.\n\nВ вашей адресной книге вы увидите, кто использует Linphone, а ваши друзья смогут узнать, что вы также зарегистрированы в Linphone.\n Номер телефона можно использовать только с одним аккаунтом Linphone.\n\nЕсли ваш номер уже был связан с другим аккаунтом, но вы предпочитаете использовать его, просто свяжите его сейчас, и ваш номер будет автоматически перенесен в этот аккаунт. Слишком много SMS было отправлено на этот номер за короткий промежуток времени, повторите попытку через 24 часа. + Аккаунт не существует Недопустимый адрес электронной почты Аккаунт уже существует @@ -450,8 +451,8 @@ Всегда Если он легче максимального размера Скрыть пустые чаты - Скрыть чаты из удалённых настроек прокси - Если у вас есть отсутствующие чаты, то попробуйте снять этот флажок + Скрыть чаты из удаленных настроек прокси + Если у вас есть отсутствующие чаты, попробуйте снять флажок с этого параметра. Сеть Использовать только WiFi diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index cbde3df58..8150a74f2 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -243,6 +243,7 @@ Ladda ner Välj en konversation eller skapa en ny Förtroende har nekats. Ring ett samtal för att starta autentiseringsprocessen igen. + Kan inte öppna filen, inget program tillgängligt för detta format. Ansluten Inte ansluten @@ -286,6 +287,7 @@ Jitterbuffert: Kodare: Avkodare: + Spelarfilter: Visningsfilter: Ring Vill du ta bort de valda samtalsloggarna? @@ -394,6 +396,7 @@ Kodek Video + Kamera Videoöverlagring Visningsvideo i överlagring när det är utanför applikationen Använd front-kamera @@ -407,6 +410,8 @@ Bandbreddsgräns i kbit/s Kodek + Närvaroinformation i lokal kontakt + Infogar informationsgenvägar från Linphone-kontakten i lokala Android-kontakter Kontakt Visa kontaktorganisation diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 3db27da5e..bc704dbc3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -129,6 +129,7 @@ \nВашим друзям буде простіше зконтактуватися з вами, якщо ви зв\'яжете свою обліківку зі своїм номером телефону.\n\nУ вашій адресній книзі ви побачите, хто використовує Linphone, а ваші друзі зможуть дізнатися, що ви також зареєстровані в Linphone.\n Номер телефону можна використовувати лише з однією обліківкою Linphone.\n\nЯкщо ваш номер вже був зв\'язаний з иншою обліківкою, але ви волієте використовувати його, просто зв\'яжіть його зараз, й ваш номер буде автоматично перенесено в цю обліківку. Забагато SMS було відправлено на цей номер за короткий проміжок часу, повторіть спробу через 24 години. + Обліківки не існує Неприпустима адреса електронної пошти Обліківка вже існує @@ -243,6 +244,7 @@ Стягнути Оберіть бесіду або створіть нову У довірі було відмовлено. Зробіть виклик, аби почати процес автентифікації знову. + Не вдається відкрити файл, немає додатків для цього формату. З\'єднано Не з\'єднано @@ -399,6 +401,7 @@ Кодеки Відео + Камера Накладення відео Показувати відеовиклик накладуванням на сторонньому застосунку Використовувати фронтальну камеру diff --git a/app/src/main/res/values/non_localizable_custom.xml b/app/src/main/res/values/non_localizable_custom.xml index b94817cc8..9937cac61 100644 --- a/app/src/main/res/values/non_localizable_custom.xml +++ b/app/src/main/res/values/non_localizable_custom.xml @@ -34,6 +34,7 @@ true false true + true EEE d MMM @@ -116,6 +117,7 @@ true true true + true false @@ -124,13 +126,17 @@ false false - + true + 1000 + 7000 + + false + + true true 86400000 - 1000 - 7000 false diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 169f146b4..ebd054773 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -134,6 +134,7 @@ \nYour friends will find you more easily if you link your account to your phone number\n\nYou will see in your address book who is using Linphone and your friends will know that they can reach you on Linphone as well.\n You can only use your phone number with one Linphone account.\n\nIf you had already linked your number to an other account but you prefer to use this one, simply link it now and your number will automatically be moved to this account. Too much SMS have been sent to this number in a short period of time, try again in 24 hours. + Account doesn\'t exist Invalid email diff --git a/build.gradle b/build.gradle index b0c72ac51..fa0a6b06f 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.google.gms:google-services:4.3.2' classpath "com.diffplug.spotless:spotless-plugin-gradle:3.24.2"