LinphoneManager.cs 40 KB
Newer Older
1
using Linphone.Agents;
2
using Linphone.Controls;
3
using Linphone.Core;
4 5 6
using Linphone.Core.OutOfProcess;
using Linphone.Resources;
using Linphone.Views;
7
using Microsoft.Phone.Controls;
8 9
using Microsoft.Phone.Net.NetworkInformation;
using Microsoft.Phone.Networking.Voip;
10
using System;
Sylvain Berfini's avatar
Sylvain Berfini committed
11
using System.Collections.Generic;
12
using System.Diagnostics;
Sylvain Berfini's avatar
Sylvain Berfini committed
13
using System.Linq;
14 15
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
16
using System.Threading.Tasks;
17 18
using System.Windows;
using System.Windows.Controls.Primitives;
19
using System.Xml.Linq;
20
using Windows.Phone.Devices.Notification;
21
using Windows.Phone.Media.Capture;
Ghislain MARY's avatar
Ghislain MARY committed
22
using Windows.Phone.Media.Devices;
23
using Windows.Phone.Networking.Voip;
Sylvain Berfini's avatar
Sylvain Berfini committed
24 25 26

namespace Linphone.Model
{
27 28 29
    /// <summary>
    /// Utility class to handle most of the LinphoneCore (and more globally the C++/CX API) methods calls.
    /// </summary>
30
    public sealed class LinphoneManager : LinphoneCoreListener
Sylvain Berfini's avatar
Sylvain Berfini committed
31
    {
32
        #region Network state management
Sylvain Berfini's avatar
Sylvain Berfini committed
33
        private bool lastNetworkState;
34 35
        private void OnNetworkStatusChanged(object sender, NetworkNotificationEventArgs e)
        {
Sylvain Berfini's avatar
Sylvain Berfini committed
36 37 38 39
            if (lastNetworkState != DeviceNetworkInformation.IsNetworkAvailable)
            {
                lastNetworkState = DeviceNetworkInformation.IsNetworkAvailable;
                Debug.WriteLine("[LinphoneManager] Network state changed:" + (lastNetworkState ? "Available" : "Unavailable"));
40 41
                if (lastNetworkState)
                {
42
                    ConfigureTunnel();
43
                }
Sylvain Berfini's avatar
Sylvain Berfini committed
44 45
                LinphoneCore.SetNetworkReachable(lastNetworkState);
            }
46
        }
47 48 49 50 51 52 53
        #endregion

        #region Class properties
        private LinphoneManager()
        {
            LastKnownState = Linphone.Core.RegistrationState.RegistrationNone;
        }
54

55
        private static LinphoneManager singleton;
56 57 58
        /// <summary>
        /// Static instance of the class.
        /// </summary>
Sylvain Berfini's avatar
Sylvain Berfini committed
59 60
        public static LinphoneManager Instance
        {
61 62 63 64 65 66 67
            get
            {
                if (LinphoneManager.singleton == null)
                    LinphoneManager.singleton = new LinphoneManager();

                return LinphoneManager.singleton;
            }
Sylvain Berfini's avatar
Sylvain Berfini committed
68 69
        }

70 71 72 73 74 75 76 77 78 79 80
        /// <summary>
        /// Quick accessor for the LinphoneCoreFactory object through the Oop server.
        /// </summary>
        public LinphoneCoreFactory LinphoneCoreFactory
        {
            get
            {
                return server.LinphoneCoreFactory;
            }
        }

81 82 83
        /// <summary>
        /// Quick accessor for the LinphoneCore object through the OoP server.
        /// </summary>
84 85 86 87 88 89 90 91
        public LinphoneCore LinphoneCore
        {
            get
            {
                return server.LinphoneCore;
            }
        }

92
        /// <summary>
Sylvain Berfini's avatar
Sylvain Berfini committed
93
        /// Call coordinator used to manage system VoIP call objects.
94
        /// </summary>
95
        public VoipCallCoordinator CallController
96 97 98
        {
            get
            {
99
                return VoipCallCoordinator.GetDefault();
100 101 102
            }
        }

103
        private RegistrationState _lastKnownState;
104 105 106
        /// <summary>
        /// Used to set the default registration state on the status bar when the view is changed.
        /// </summary>
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
        public RegistrationState LastKnownState {
            get
            {
                if (isLinphoneRunning && LinphoneCore.GetDefaultProxyConfig() != null) 
                {
                    _lastKnownState = LinphoneCore.GetDefaultProxyConfig().GetState();
                }
                return _lastKnownState;
            }

            set
            {
                _lastKnownState = value;
            }
        }
122

Sylvain Berfini's avatar
Sylvain Berfini committed
123 124 125
        /// <summary>
        /// Simple listener to notify pages' viewmodel when a call ends or starts
        /// </summary>
126
        public CallControllerListener CallListener { get; set; }
127 128 129 130 131

        /// <summary>
        /// Simple listener to notify the echo canceller calibration status
        /// </summary>
        public EchoCalibratorListener ECListener { get; set; }
132
        #endregion
133

134
        #region Background Process
135 136
        private bool BackgroundProcessConnected;

137 138 139
        // An event that indicates that the UI process is no longer connected to the background process 
        private EventWaitHandle uiDisconnectedEvent;

140
        // A proxy to the server object in the background agent host process 
141
        private Server server = null;
142 143

        // A timespan representing fifteen seconds 
144
        private static readonly TimeSpan twentySecs = new TimeSpan(0, 0, 20);
145 146

        // A timespan representing an indefinite wait 
147
        private static readonly TimeSpan indefiniteWait = new TimeSpan(0, 0, 0, 0, -1);
148

149 150 151 152 153
        /// <summary>
        /// Used to know when linphoneCore has been initialized
        /// </summary>
        public bool isLinphoneRunning = false;

154
        /// <summary>
155
        /// Starts and connects the UI to the background process.
156
        /// </summary>
157 158 159 160 161 162 163 164 165 166 167
        public void ConnectBackgroundProcessToInterface()
        {
            if (BackgroundProcessConnected)
            {
                Debug.WriteLine("[LinphoneManager] Background process already connected to interface");
                return;
            }

            int backgroundProcessID;
            try
            {
168
                VoipBackgroundProcess.Launch(out backgroundProcessID);
169 170 171 172 173 174 175
            }
            catch (Exception e)
            {
                Debug.WriteLine("[LinphoneManager] Error launching VoIP background process. Exception: " + e.Message);
                throw;
            }

176 177 178 179
            // Wait for the background process to become ready 
            string backgroundProcessReadyEventName = Globals.GetBackgroundProcessReadyEventName((uint)backgroundProcessID);
            using (EventWaitHandle backgroundProcessReadyEvent = new EventWaitHandle(initialState: false, mode: EventResetMode.ManualReset, name: backgroundProcessReadyEventName))
            {
180
                TimeSpan timeout = twentySecs;
181 182 183
                if (!backgroundProcessReadyEvent.WaitOne(timeout))
                {
                    // We timed out - something is wrong 
184
                    throw new InvalidOperationException(string.Format("The background process ({0}) did not become ready in {1} seconds", backgroundProcessID, timeout.Seconds));
185 186 187
                }
                else
                {
188
                    Debug.WriteLine("[LinphoneManager] Background process {0} is ready", backgroundProcessID);
189 190 191 192 193 194 195
                }
            }
            // The background process is now ready. 
            // It is possible that the background process now becomes "not ready" again, but the chances of this happening are slim, 
            // and in that case, the following statement would fail - so, at this point, we don't explicitly guard against this condition. 

            // Create an instance of the server in the background process. 
196 197 198
            BackgroundManager.Instance.OopServer = null;
            server = (Server)WindowsRuntimeMarshal.GetActivationFactory(typeof(Server)).ActivateInstance();
            BackgroundManager.Instance.OopServer = server;
199 200

            // Un-set an event that indicates that the UI process is disconnected from the background process. 
201 202
            // The background process waits for this event to get set before shutting down. 
            // This ensures that the background agent host process doesn't shut down while the UI process is connected to it. 
203
            string uiDisconnectedEventName = Globals.GetUiDisconnectedEventName((uint)backgroundProcessID);
204 205
            uiDisconnectedEvent = new EventWaitHandle(initialState: false, mode: EventResetMode.ManualReset, name: uiDisconnectedEventName);
            uiDisconnectedEvent.Reset();
206

207 208 209 210
            BackgroundProcessConnected = true;
            Debug.WriteLine("[LinphoneManager] Background process connected to interface");
        }

211
        /// <summary>
212
        /// Disconnects the UI from the background process.
213
        /// </summary>
214 215 216 217 218 219 220 221
        public void DisconnectBackgroundProcessFromInterface()
        {
            if (!BackgroundProcessConnected)
            {
                Debug.WriteLine("[LinphoneManager] Background process not connected to interface yet");
                return;
            }

222
            // Cancel any incoming call
223 224
            try
            {
225
                LinphoneCore.CoreListener = null;
226 227 228 229 230 231 232 233
                if (LinphoneCore.GetCallsNb() == 1)
                {
                    LinphoneCall call = (LinphoneCall)LinphoneCore.GetCalls()[0];
                    if (call.GetState() == LinphoneCallState.IncomingReceived)
                        LinphoneCore.TerminateCall(call);
                }
            }
            catch (Exception)
234
            {
235 236 237
                // Catch "The RPC server is unavailable." exceptions that occur sometimes at this point.
                // This is to clarify why the access to the RPC server is not reliable when called application
                // deactivation handler...
238 239
            }

240
            BackgroundProcessConnected = false;
241
            isLinphoneRunning = false;
242
            Debug.WriteLine("[LinphoneManager] Background process disconnected from interface");
243 244

            // From this point onwards, it is no longer safe to use any objects in the background process, 
245
            // or for the background process to call back into this process.
246
            server = null;
247
            BackgroundManager.Instance.OopServer = null;
248 249

            // Lastly, set the event that indicates that the UI is no longer connected to the background process. 
250
            if (uiDisconnectedEvent == null)
251 252
                throw new InvalidOperationException("The ConnectUi method must be called before this method is called");

253 254
            uiDisconnectedEvent.Set();
            uiDisconnectedEvent.Dispose();
255 256
            uiDisconnectedEvent = null;
        }
257
        #endregion
258

259
        #region Linphone Core init
260
        /// <summary>
261
        /// Creates a new LinphoneCore (if not created yet) using a LinphoneCoreFactory.
262
        /// </summary>
263
        public async Task InitLinphoneCore()
264
        {
Ghislain MARY's avatar
Ghislain MARY committed
265
            if ((server.LinphoneCoreFactory != null) && (server.LinphoneCore != null))
266 267
            {
                // Reconnect the listeners when coming back from background mode
268
                Debug.WriteLine("[LinphoneManager] LinphoneCore already created, skipping");
269

270
                server.LinphoneCore.CoreListener = this;
271
                isLinphoneRunning = true;
272
                // Set user-agent because it is not set if coming back from background mode
273
                server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
274
                return;
275
            }
276

277
            Debug.WriteLine("[LinphoneManager] Creating LinphoneCore");
278
            await SettingsManager.InstallConfigFile();
279
            LpConfig config = server.LinphoneCoreFactory.CreateLpConfig(SettingsManager.GetConfigPath(), SettingsManager.GetFactoryConfigPath());
280
            ConfigureLogger();
281
            server.LinphoneCoreFactory.CreateLinphoneCore(this, config);
Ghislain MARY's avatar
Ghislain MARY committed
282
            server.LinphoneCore.SetRootCA("Assets/rootca.pem");
283 284
            Debug.WriteLine("[LinphoneManager] LinphoneCore created");

285 286 287
            AudioRoutingManager.GetDefault().AudioEndpointChanged += AudioEndpointChanged;
            CallController.MuteRequested += MuteRequested;
            CallController.UnmuteRequested += UnmuteRequested;
288

289
            if (server.LinphoneCore.IsVideoSupported())
Sylvain Berfini's avatar
Sylvain Berfini committed
290
            {
291
                DetectCameras();
Sylvain Berfini's avatar
Sylvain Berfini committed
292
            }
293

294
            server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
295
            AddPushInformationsToContactParams();
296

Sylvain Berfini's avatar
Sylvain Berfini committed
297
            lastNetworkState = DeviceNetworkInformation.IsNetworkAvailable;
298
            server.LinphoneCore.SetNetworkReachable(lastNetworkState);
Sylvain Berfini's avatar
Sylvain Berfini committed
299
            DeviceNetworkInformation.NetworkAvailabilityChanged += new EventHandler<NetworkNotificationEventArgs>(OnNetworkStatusChanged);
300
            ConfigureTunnel();
301

302
            server.LinphoneCore.IterateEnabled = true;
303
            isLinphoneRunning = true;
304 305
        }

306 307 308 309 310 311 312
        /// <summary>
        /// Sets the push notif infos into proxy config contacts params
        /// </summary>
        public void AddPushInformationsToContactParams()
        {
            if (server.LinphoneCore.GetDefaultProxyConfig() != null)
            {
313 314 315 316 317 318 319
                string host = null, token = null;
                try
                {
                    host = ((App)App.Current).PushChannelUri.Host;
                    token = ((App)App.Current).PushChannelUri.AbsolutePath;
                }
                catch { }
320 321 322 323 324 325 326

                if (host == null || token == null)
                {
                    Logger.Warn("Can't set the PN params: {0} {1}", host, token);
                    return;
                }

327 328 329 330
                if (Customs.AddPasswordInContactsParams)
                {
                    SIPAccountSettingsManager sip = new SIPAccountSettingsManager();
                    sip.Load();
331
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("pwd=" + sip.Password + ";app-id=" + host + ";pn-type=wp;pn-tok=" + token);
332 333 334 335 336
                }
                else
                {
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("app-id=" + host + ";pn-type=wp;pn-tok=" + token);
                }
337 338 339
            }
        }

340 341 342 343 344
        /// <summary>
        /// Configures the Logger
        /// </summary>
        public void ConfigureLogger()
        {
Ghislain MARY's avatar
Ghislain MARY committed
345 346
            // To have the debug output in the debugger use the following commented configure and set your debugger to native mode
            //server.BackgroundModeLogger.Configure(SettingsManager.isDebugEnabled, OutputTraceDest.Debugger, "");
347
            // Else output the debug traces to a file
348 349
            ApplicationSettingsManager appSettings = new ApplicationSettingsManager();
            appSettings.Load();
350
            server.BackgroundModeLogger.Configure(appSettings.DebugEnabled, appSettings.LogDestination, appSettings.LogOption);
Ghislain MARY's avatar
Ghislain MARY committed
351
            server.LinphoneCoreFactory.OutputTraceListener = server.BackgroundModeLogger;
352
            server.LinphoneCoreFactory.SetLogLevel(appSettings.LogLevel);
Ghislain MARY's avatar
Ghislain MARY committed
353
            Logger.Instance.TraceListener = server.BackgroundModeLogger;
354
        }
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

        /// <summary>
        /// Configures the Tunnel using the given mode
        /// </summary>
        /// <param name="mode">mode to apply</param>
        public static void ConfigureTunnel(String mode)
        {
            if (LinphoneManager.Instance.LinphoneCore.IsTunnelAvailable())
            {
                Tunnel tunnel = LinphoneManager.Instance.LinphoneCore.GetTunnel();
                if (tunnel != null)
                {
                    if (mode == AppResources.TunnelModeDisabled)
                    {
                        tunnel.Enable(false);
                    }
                    else if (mode == AppResources.TunnelModeAlways)
                    {
                        tunnel.Enable(true);
                    }
                    else if (mode == AppResources.TunnelModeAuto)
                    {
                        tunnel.Enable(false);
                        tunnel.AutoDetect();
                    }
                    else if (mode == AppResources.TunnelMode3GOnly)
                    {
                        if (DeviceNetworkInformation.IsWiFiEnabled)
                        {
                            tunnel.Enable(false);
                        }
                        else if (DeviceNetworkInformation.IsCellularDataEnabled)
                        {
                            tunnel.Enable(true);
                        }
                        else
                        {
                            tunnel.Enable(false);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Configures the Tunnel using the current setting value
        /// </summary>
        public void ConfigureTunnel()
        {
            NetworkSettingsManager settings = new NetworkSettingsManager();
            settings.Load();
            ConfigureTunnel(settings.TunnelMode);
        }
408
        #endregion
409

410
        #region CallLogs
Sylvain Berfini's avatar
Sylvain Berfini committed
411
        private List<CallLog> _history;
412

413 414 415 416 417 418 419 420 421 422
        /// <summary>
        /// Gets the latest called address or number
        /// </summary>
        /// <returns>null if there isn't any</returns>
        public string GetLastCalledNumber()
        {
            foreach (LinphoneCallLog log in LinphoneManager.Instance.LinphoneCore.GetCallLogs())
            {
                if (log.GetDirection() == CallDirection.Outgoing)
                {
423
                    return log.GetTo().AsStringUriOnly();
424 425 426 427 428
                }
            }
            return null;
        }

Sylvain Berfini's avatar
Sylvain Berfini committed
429
        /// <summary>
430
        /// Get the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
431 432
        /// </summary>
        /// <returns>A list of CallLogs, each one representing a type of calls (All, Missed, ...)</returns>
Sylvain Berfini's avatar
Sylvain Berfini committed
433
        public List<CallLog> GetCallsHistory()
Sylvain Berfini's avatar
Sylvain Berfini committed
434
        {
Sylvain Berfini's avatar
Sylvain Berfini committed
435
            _history = new List<CallLog>();
Sylvain Berfini's avatar
Sylvain Berfini committed
436

437 438 439 440 441
            if (LinphoneCore.GetCallLogs() != null)
            {
                foreach (LinphoneCallLog log in LinphoneCore.GetCallLogs())
                {
                    string from = log.GetFrom().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
442
                    if (from.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
443
                        from = log.GetFrom().AsStringUriOnly().Replace("sip:", "");
444 445

                    string to = log.GetTo().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
446
                    if (to.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
447
                        to = log.GetTo().AsStringUriOnly().Replace("sip:", "");
448 449

                    bool isMissed = log.GetStatus() == LinphoneCallStatus.Missed;
Sylvain Berfini's avatar
Sylvain Berfini committed
450 451 452
                    long startDate = log.GetStartDate();
                    CallLog callLog = new CallLog(log, from, to, log.GetDirection() == CallDirection.Incoming, isMissed, startDate);
                    _history.Add(callLog);
453 454 455
                }
            }

Sylvain Berfini's avatar
Sylvain Berfini committed
456 457 458 459
            return _history;
        }

        /// <summary>
460
        /// Remove one or many entries from the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
461
        /// </summary>
462
        /// <param name="logsToRemove">A list of CallLog to remove from history</param>
Sylvain Berfini's avatar
Sylvain Berfini committed
463
        /// <returns>A list of CallLogs, without the removed entries</returns>
464
        public void RemoveCallLogs(IEnumerable<CallLog> logsToRemove)
Sylvain Berfini's avatar
Sylvain Berfini committed
465 466 467
        {
            // When removing log from history, it will be removed from logsToRemove list too. 
            // Using foreach causing the app to crash on a InvalidOperationException, so we are using while
468
            for (int i = 0; i < logsToRemove.Count(); i++)
Sylvain Berfini's avatar
Sylvain Berfini committed
469
            {
470
                CallLog logToRemove = logsToRemove.ElementAt(i);
471
                LinphoneCore.RemoveCallLog(logToRemove.NativeLog as LinphoneCallLog);
Sylvain Berfini's avatar
Sylvain Berfini committed
472 473 474 475
            }
        }

        /// <summary>
476
        /// Remove all calls' history from LinphoneCore.
Sylvain Berfini's avatar
Sylvain Berfini committed
477
        /// </summary>
478
        /// <returns>An empty list</returns>
479
        public void ClearCallLogs()
Sylvain Berfini's avatar
Sylvain Berfini committed
480
        {
481
            LinphoneCore.ClearCallLogs();
Sylvain Berfini's avatar
Sylvain Berfini committed
482
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
483
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
484

Sylvain Berfini's avatar
Sylvain Berfini committed
485
        #region Call Management
Sylvain Berfini's avatar
Sylvain Berfini committed
486
        /// <summary>
487
        /// Start a new call to a sip address.
Sylvain Berfini's avatar
Sylvain Berfini committed
488 489 490 491
        /// </summary>
        /// <param name="sipAddress">SIP address to call</param>
        public void NewOutgoingCall(String sipAddress)
        {
492
            LinphoneCall LCall = LinphoneCore.Invite(sipAddress);
Sylvain Berfini's avatar
Sylvain Berfini committed
493
        }
494 495

        /// <summary>
496
        /// Stops the current call if any.
497 498 499
        /// </summary>
        public void EndCurrentCall()
        {
500 501
            LinphoneCall call = LinphoneCore.GetCurrentCall();
            if (call != null)
502
            {
503
                LinphoneCore.TerminateCall(call);
504
            }
505 506 507 508 509 510 511 512 513 514
            else
            {
                foreach (LinphoneCall lCall in LinphoneCore.GetCalls())
                {
                    if (lCall.GetState() == LinphoneCallState.Paused)
                    {
                        LinphoneCore.TerminateCall(lCall);
                    }
                }
            }
515 516
        }

517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
        /// <summary>
        /// Enable or disable sound capture using the device microphone
        /// </summary>
        public void MuteMic(Boolean isMicMuted)
        {
            BaseModel.UIDispatcher.BeginInvoke(() =>
            {
                if (isMicMuted)
                    CallController.NotifyMuted();
                else
                    CallController.NotifyUnmuted();

                if (CallListener != null)
                    CallListener.MuteStateChanged(isMicMuted);
            });

            LinphoneCore.MuteMic(isMicMuted);
        }

        private void UnmuteRequested(VoipCallCoordinator sender, MuteChangeEventArgs args)
        {
538
            Logger.Msg("[LinphoneManager] Unmute requested");
539 540 541 542 543
            MuteMic(true);
        }

        private void MuteRequested(VoipCallCoordinator sender, MuteChangeEventArgs args)
        {
544
            Logger.Msg("[LinphoneManager] Mute requested");
545 546 547
            MuteMic(false);
        }

548
        /// <summary>
549
        /// Pauses the current call if any and if it's running.
550 551 552
        /// </summary>
        public void PauseCurrentCall()
        {
553 554 555 556 557
            if (LinphoneCore.GetCallsNb() > 0)
            {
                LinphoneCall call = LinphoneCore.GetCurrentCall();
                LinphoneCore.PauseCall(call);
            }
558 559 560
        }

        /// <summary>
561
        /// Resume the current call if any and if it's paused.
562 563 564
        /// </summary>
        public void ResumeCurrentCall()
        {
565 566 567 568 569
            foreach (LinphoneCall call in LinphoneCore.GetCalls()) {
                if (call.GetState() == LinphoneCallState.Paused)
                {
                    LinphoneCore.ResumeCall(call);
                }
570
            }
571
        }
572 573 574

        private void CallResumeRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
575
            Logger.Msg("[LinphoneManager] Resume requested");
576 577 578 579 580
            ResumeCurrentCall();
        }

        private void CallHoldRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
581
            Logger.Msg("[LinphoneManager] Pause requested");
582 583
            PauseCurrentCall();
        }
584
        #endregion
585

Ghislain MARY's avatar
Ghislain MARY committed
586 587 588
        #region Audio route handling
        private void AudioEndpointChanged(AudioRoutingManager sender, object args)
        {
Ghislain MARY's avatar
Ghislain MARY committed
589
            Logger.Msg("[LinphoneManager] AudioEndpointChanged:" + sender.GetAudioEndpoint().ToString());
Ghislain MARY's avatar
Ghislain MARY committed
590 591
        }

592 593 594 595
        /// <summary>
        /// Enables the speaker in the current call
        /// </summary>
        /// <param name="enable">true to enable, false to disable</param>
Ghislain MARY's avatar
Ghislain MARY committed
596 597 598 599 600 601 602 603 604 605 606 607 608
        public void EnableSpeaker(bool enable)
        {
            if (enable)
            {
                AudioRoutingManager.GetDefault().SetAudioEndpoint(AudioRoutingEndpoint.Speakerphone);
            }
            else
            {
                AudioRoutingManager.GetDefault().SetAudioEndpoint(AudioRoutingEndpoint.Earpiece);
            }
        }
        #endregion

Ghislain MARY's avatar
Ghislain MARY committed
609 610
        #region Video handling

Ghislain MARY's avatar
Ghislain MARY committed
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
        /// <summary>
        /// Enables disables video.
        /// </summary>
        /// <param name="enable">Wether to enable or disable video</param>
        /// <returns>true if the operation has been successful, false otherwise</returns>
        public bool EnableVideo(bool enable)
        {
            if (LinphoneCore.IsInCall())
            {
                LinphoneCall call = LinphoneCore.GetCurrentCall();
                if (enable != call.IsCameraEnabled())
                {
                    LinphoneCallParams parameters = call.GetCurrentParamsCopy();
                    parameters.EnableVideo(enable);
                    if (enable)
                    {
                        // TODO: Handle bandwidth limitation
                    }
                    LinphoneCore.UpdateCall(call, parameters);
                    return true;
                }
            }
            return false;
        }

Ghislain MARY's avatar
Ghislain MARY committed
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
        private int nbCameras = 0;
        private String frontCamera = null;
        private String backCamera = null;

        private void DetectCameras()
        {
            int nbCameras = 0;
            foreach (String device in LinphoneCore.GetVideoDevices())
            {
                if (device.EndsWith(CameraSensorLocation.Front.ToString()))
                {
                    frontCamera = device;
                    nbCameras++;
                }
                else if (device.EndsWith(CameraSensorLocation.Back.ToString()))
                {
                    backCamera = device;
                    nbCameras++;
                }
            }
            String currentDevice = LinphoneCore.GetVideoDevice();
            if ((currentDevice != frontCamera) && (currentDevice != backCamera))
            {
                if (frontCamera != null)
                {
                    LinphoneCore.SetVideoDevice(frontCamera);
                }
                else if (backCamera != null)
                {
                    LinphoneCore.SetVideoDevice(backCamera);
                }
            }
            this.nbCameras = nbCameras;
        }

        /// <summary>
        /// Gets the number of cameras available on the device (int).
        /// </summary>
        public int NumberOfCameras
        {
            get
            {
                return nbCameras;
            }
        }

        /// <summary>
        /// Toggles the camera used for video capture.
        /// </summary>
        public void ToggleCameras()
        {
            if (NumberOfCameras >= 2)
            {
                String currentDevice = LinphoneCore.GetVideoDevice();
                if (currentDevice == frontCamera)
                {
                    LinphoneCore.SetVideoDevice(backCamera);
                }
                else if (currentDevice == backCamera)
                {
                    LinphoneCore.SetVideoDevice(frontCamera);
                }
                if (LinphoneCore.IsInCall())
                {
                    LinphoneCall call = LinphoneCore.GetCurrentCall();
Ghislain MARY's avatar
Ghislain MARY committed
701
                    LinphoneCore.UpdateCall(call, null);
Ghislain MARY's avatar
Ghislain MARY committed
702 703 704 705 706
                }
            }
        }
        #endregion

707
        #region LinphoneCoreListener Callbacks
708 709 710
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
Sylvain Berfini's avatar
Sylvain Berfini committed
711
        public void AuthInfoRequested(string realm, string username, string domain)
712
        {
Sylvain Berfini's avatar
Sylvain Berfini committed
713
            Logger.Msg("[LinphoneManager] Auth info requested: realm=" + realm + ", username=" + username + ", domain=" + domain);
714 715
        }

716 717 718
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
719 720
        public void GlobalState(GlobalState state, string message)
        {
Ghislain MARY's avatar
Ghislain MARY committed
721
            Logger.Msg("[LinphoneManager] Global state changed: " + state.ToString() + ", message=" + message);
722 723
        }

724 725 726
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
727 728
        public void CallState(LinphoneCall call, LinphoneCallState state)
        {
Ghislain MARY's avatar
Ghislain MARY committed
729
            string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
730

Ghislain MARY's avatar
Ghislain MARY committed
731 732 733 734 735
            Logger.Msg("[LinphoneManager] Call state changed: " + sipAddress + " => " + state.ToString());
            if (state == LinphoneCallState.OutgoingProgress)
            {
                Logger.Msg("[LinphoneManager] Outgoing progress");
                BaseModel.UIDispatcher.BeginInvoke(() =>
736
                {
737
                    LookupForContact(call);
738

739
                    if (CallListener != null)
740
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
741 742 743 744 745 746
                });
            }
            else if (state == LinphoneCallState.IncomingReceived)
            {
                Logger.Msg("[LinphoneManager] Incoming received"); 
                BaseModel.UIDispatcher.BeginInvoke(() =>
747
                {
748
                    if (false) //TODO: Find a proper way to let the user choose between the two.
749 750 751 752 753
                    {
                        BaseModel.CurrentPage.NavigationService.Navigate(new Uri("/Views/IncomingCall.xaml?sip=" + call.GetRemoteAddress().AsStringUriOnly(), UriKind.RelativeOrAbsolute));
                        //Remove the current page from the back stack to avoid duplicating him after
                        BaseModel.CurrentPage.NavigationService.RemoveBackEntry();
                    }
754

755
                    LookupForContact(call);
Ghislain MARY's avatar
Ghislain MARY committed
756 757 758 759 760 761
                });
            }
            else if (state == LinphoneCallState.Connected)
            {
                Logger.Msg("[LinphoneManager] Connected");
                BaseModel.UIDispatcher.BeginInvoke(() =>
762
                {
763 764
                    if (CallListener != null)
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
765 766 767 768 769 770
                });
            }
            else if (state == LinphoneCallState.CallEnd || state == LinphoneCallState.Error)
            {
                Logger.Msg("[LinphoneManager] Call ended");
                BaseModel.UIDispatcher.BeginInvoke(() =>
771
                {
772
                    if (CallListener != null)
773
                        CallListener.CallEnded(call);
Ghislain MARY's avatar
Ghislain MARY committed
774 775 776 777 778 779
                });
            }
            else if (state == LinphoneCallState.Paused || state == LinphoneCallState.PausedByRemote)
            {
                Logger.Msg("[LinphoneManager] Call paused");
                BaseModel.UIDispatcher.BeginInvoke(() =>
780 781
                {
                    if (CallListener != null)
782
                        CallListener.PauseStateChanged(call, true);
Ghislain MARY's avatar
Ghislain MARY committed
783 784 785 786 787 788
                });
            }
            else if (state == LinphoneCallState.StreamsRunning)
            {
                Logger.Msg("[LinphoneManager] Call running");
                BaseModel.UIDispatcher.BeginInvoke(() =>
789 790
                {
                    if (CallListener != null)
791
                        CallListener.PauseStateChanged(call, false);
Ghislain MARY's avatar
Ghislain MARY committed
792 793 794 795 796 797
                });
            }
            else if (state == LinphoneCallState.Released)
            {
                Logger.Msg("[LinphoneManager] Call released");
                BaseModel.UIDispatcher.BeginInvoke(() =>
798
                {
799 800
                    //Update tile
                    UpdateLiveTile();
Ghislain MARY's avatar
Ghislain MARY committed
801 802
                });
            }
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
            else if (state == LinphoneCallState.UpdatedByRemote)
            {
                Boolean videoAdded = false;
                VideoPolicy policy = LinphoneManager.Instance.LinphoneCore.GetVideoPolicy();
                LinphoneCallParams remoteParams = call.GetRemoteParams();
                LinphoneCallParams localParams = call.GetCurrentParamsCopy();
                if (!policy.AutomaticallyAccept && remoteParams.IsVideoEnabled() && !localParams.IsVideoEnabled() && !LinphoneManager.Instance.LinphoneCore.IsInConference())
                {
                    LinphoneManager.Instance.LinphoneCore.DeferCallUpdate(call);
                    videoAdded = true;
                }
                BaseModel.UIDispatcher.BeginInvoke(() =>
                {
                    if (CallListener != null)
                        CallListener.CallUpdatedByRemote(call, videoAdded);
                });
            }
820 821
        }

822 823 824
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
825 826
        public void RegistrationState(LinphoneProxyConfig config, RegistrationState state, string message)
        {
827 828 829
            if (config == null)
                return;

830 831
            BaseModel.UIDispatcher.BeginInvoke(() =>
            {
832 833 834 835 836 837 838
                try
                {
                    Logger.Msg("[LinphoneManager] Registration state changed: " + state.ToString() + ", message=" + message + " for identity " + config.GetIdentity());
                    LastKnownState = state;
                    if (BasePage.StatusBar != null)
                        BasePage.StatusBar.RefreshStatus(state);
                }
839
                catch { }
840
            });
841 842
        }

843 844 845
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
846
        public void DTMFReceived(LinphoneCall call, Char dtmf)
847 848 849 850
        {

        }

851 852 853
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
854
        public void EcCalibrationStatus(EcCalibratorStatus status, int delayMs)
855
        {
856 857 858 859 860 861 862 863 864
            Logger.Msg("[LinphoneManager] Echo canceller calibration status: " + status.ToString());
            if (status == EcCalibratorStatus.Done)
            {
                Logger.Msg("[LinphoneManager] Echo canceller delay: {0} ms", delayMs);
            }
            if (ECListener != null)
            {
                ECListener.ECStatusNotified(status, delayMs);
            }
865 866
        }

867 868 869
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
870 871 872 873 874
        public void CallEncryptionChanged(LinphoneCall call, bool encrypted, string authenticationToken)
        {

        }

875 876 877
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
878 879 880 881
        public void CallStatsUpdated(LinphoneCall call, LinphoneCallStats stats)
        {

        }
882

883 884 885 886 887
        /// <summary>
        /// Listener to let a view to be notified by LinphoneManager when a new message arrives.
        /// </summary>
        public MessageReceivedListener MessageListener { get; set; }

888 889 890
        /// <summary>
        /// Custom message box to display incoming messages when not in chat view
        /// </summary>
891
        public CustomMessageBox MessageReceivedNotification { get; set; }
892

893 894 895 896 897
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
        public void MessageReceived(LinphoneChatMessage message)
        {
898 899
            string sipAddress = message.GetFrom().AsStringUriOnly().Replace("sip:", "");
            Logger.Msg("[LinphoneManager] Message received from " + sipAddress + ": " + message.GetText());
900

901
            //Vibrate
902 903 904 905 906 907 908
            ChatSettingsManager settings = new ChatSettingsManager();
            settings.Load();
            if ((bool)settings.VibrateOnIncomingMessage)
            {
                VibrationDevice vibrator = VibrationDevice.GetDefault();
                vibrator.Vibrate(TimeSpan.FromSeconds(1));
            }
909

910
            if (MessageListener != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation() != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation().Equals(sipAddress))
911 912 913
            {
                MessageListener.MessageReceived(message);
            }
914 915
            else
            {
916 917 918 919 920 921 922
                BaseModel.UIDispatcher.BeginInvoke(() =>
                {
                    DateTime date = new DateTime();
                    date = date.AddYears(1969); //Timestamp is calculated from 01/01/1970, and DateTime is initialized to 01/01/0001.
                    date = date.AddSeconds(message.GetTime());
                    date = date.Add(TimeZoneInfo.Local.GetUtcOffset(date));
                    long timestamp = (date.Ticks / TimeSpan.TicksPerSecond);
923

924 925 926
                    //TODO: Temp hack to remove
                    string url = message.GetExternalBodyUrl();
                    url = url.Replace("\"", "");
927

928 929 930
                    ChatMessage msg = new ChatMessage { Message = message.GetText(), ImageURL = url, MarkedAsRead = false, IsIncoming = true, LocalContact = sipAddress, RemoteContact = "", Timestamp = timestamp };
                    DatabaseManager.Instance.Messages.InsertOnSubmit(msg);
                    DatabaseManager.Instance.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
931

932
                    //Displays the message as a popup
933 934
                    if (MessageReceivedNotification != null)
                    {
935
                        MessageReceivedNotification.Dismiss();
936 937
                    }

938
                    MessageReceivedNotification = new CustomMessageBox()
939
                    {
940 941 942 943
                        Caption = url.Length > 0 ? AppResources.ImageMessageReceived : AppResources.MessageReceived,
                        Message = url.Length > 0 ? "" : message.GetText(),
                        LeftButtonContent = AppResources.Show,
                        RightButtonContent = AppResources.Close
944
                    };
945 946 947 948 949 950 951 952 953 954 955 956

                    MessageReceivedNotification.Dismissed += (s, e) =>
                        {
                            switch (e.Result)
                            {
                                case CustomMessageBoxResult.LeftButton:
                                    BaseModel.CurrentPage.NavigationService.Navigate(new Uri("/Views/Chat.xaml?sip=" + msg.Contact, UriKind.RelativeOrAbsolute));
                                    break;
                            }
                        };

                    MessageReceivedNotification.Show();
957 958 959

                    //Update tile
                    UpdateLiveTile();
960
                });
961
            }
962
        }
963
        #endregion
964

965 966 967 968 969 970 971 972 973 974
        /// <summary>
        /// Updates the app tile to display the number of missed calls and unread chats.
        /// </summary>
        public void UpdateLiveTile()
        {
            int missedCalls = LinphoneCore.GetMissedCallsCount();
            int unreadChats = (from message in DatabaseManager.Instance.Messages where message.MarkedAsRead == false select message).ToList().Count;
            TileManager.Instance.UpdateTileWithMissedCallsAndUnreadMessages(missedCalls + unreadChats);
        }

975 976 977 978 979 980 981 982 983
        #region Contact Lookup
        private ContactManager ContactManager
        {
            get
            {
                return ContactManager.Instance;
            }
        }

984
        private void LookupForContact(LinphoneCall call)
985
        {
986
            try
987
            {
988 989
                string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
                if (call.GetRemoteAddress().GetDisplayName().Length == 0)
990
                {
991 992 993 994 995
                    if (sipAddress.StartsWith("sip:"))
                    {
                        sipAddress = sipAddress.Substring(4);
                    }
                    Logger.Msg("[LinphoneManager] Display name null, looking for remote address in contact: " + sipAddress);
996

997 998 999 1000 1001 1002 1003
                    ContactManager.ContactFound += OnContactFound;
                    ContactManager.FindContact(sipAddress);
                }
                else
                {
                    Logger.Msg("[LinphoneManager] Display name found: " + call.GetRemoteAddress().GetDisplayName());
                }
1004
            }
1005
            catch 
1006
            {
1007
                Logger.Warn("[LinphoneManager] Execption occured while looking for contact...");
1008 1009 1010 1011 1012 1013 1014 1015
            }
        }

        /// <summary>
        /// Callback called when the search on a phone number for a contact has a match
        /// </summary>
        private void OnContactFound(object sender, ContactFoundEventArgs e)
        {
1016
            if (e.ContactFound != null)
1017
            {
1018 1019 1020 1021 1022 1023 1024 1025
                Logger.Msg("[LinphoneManager] Contact found: " + e.ContactFound.DisplayName);
                ContactManager.ContactFound -= OnContactFound;

                // Store the contact name as display name for call logs
                if (LinphoneManager.Instance.LinphoneCore.GetCurrentCall() != null)
                {
                    LinphoneManager.Instance.LinphoneCore.GetCurrentCall().GetRemoteAddress().SetDisplayName(e.ContactFound.DisplayName);
                }
1026 1027 1028
            }
        }
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
1029 1030
    }
}