LinphoneManager.cs 39.5 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 223 224 225 226 227 228 229
            // Cancel any incoming call
            if (LinphoneCore.GetCallsNb() == 1)
            {
                LinphoneCall call = (LinphoneCall)LinphoneCore.GetCalls()[0];
                if (call.GetState() == LinphoneCallState.IncomingReceived)
                    LinphoneCore.TerminateCall(call);
            }

230
            BackgroundProcessConnected = false;
231
            isLinphoneRunning = false;
232
            Debug.WriteLine("[LinphoneManager] Background process disconnected from interface");
233 234

            // From this point onwards, it is no longer safe to use any objects in the background process, 
235
            // or for the background process to call back into this process.
236
            server = null;
237 238

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

242 243
            uiDisconnectedEvent.Set();
            uiDisconnectedEvent.Dispose();
244 245
            uiDisconnectedEvent = null;
        }
246
        #endregion
247

248
        #region Linphone Core init
249
        /// <summary>
250
        /// Creates a new LinphoneCore (if not created yet) using a LinphoneCoreFactory.
251
        /// </summary>
252
        public async Task InitLinphoneCore()
253
        {
Ghislain MARY's avatar
Ghislain MARY committed
254
            if ((server.LinphoneCoreFactory != null) && (server.LinphoneCore != null))
255 256
            {
                // Reconnect the listeners when coming back from background mode
257
                Debug.WriteLine("[LinphoneManager] LinphoneCore already created, skipping");
258

259
                server.LinphoneCore.CoreListener = this;
260
                isLinphoneRunning = true;
261
                // Set user-agent because it is not set if coming back from background mode
262
                server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
263
                return;
264
            }
265

266
            Debug.WriteLine("[LinphoneManager] Creating LinphoneCore");
267
            await SettingsManager.InstallConfigFile();
268
            LpConfig config = server.LinphoneCoreFactory.CreateLpConfig(SettingsManager.GetConfigPath(), SettingsManager.GetFactoryConfigPath());
269
            ConfigureLogger();
270
            server.LinphoneCoreFactory.CreateLinphoneCore(this, config);
Ghislain MARY's avatar
Ghislain MARY committed
271
            server.LinphoneCore.SetRootCA("Assets/rootca.pem");
272
            Debug.WriteLine("[LinphoneManager] LinphoneCore created");
273

274 275 276
            AudioRoutingManager.GetDefault().AudioEndpointChanged += AudioEndpointChanged;
            CallController.MuteRequested += MuteRequested;
            CallController.UnmuteRequested += UnmuteRequested;
277

278
            if (server.LinphoneCore.IsVideoSupported())
Sylvain Berfini's avatar
Sylvain Berfini committed
279
            {
280
                DetectCameras();
Sylvain Berfini's avatar
Sylvain Berfini committed
281
            }
282

283
            server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
284
            AddPushInformationsToContactParams();
285

Sylvain Berfini's avatar
Sylvain Berfini committed
286
            lastNetworkState = DeviceNetworkInformation.IsNetworkAvailable;
287
            server.LinphoneCore.SetNetworkReachable(lastNetworkState);
Sylvain Berfini's avatar
Sylvain Berfini committed
288
            DeviceNetworkInformation.NetworkAvailabilityChanged += new EventHandler<NetworkNotificationEventArgs>(OnNetworkStatusChanged);
289
            ConfigureTunnel();
290 291

            isLinphoneRunning = true;
292 293
        }

294 295 296 297 298 299 300
        /// <summary>
        /// Sets the push notif infos into proxy config contacts params
        /// </summary>
        public void AddPushInformationsToContactParams()
        {
            if (server.LinphoneCore.GetDefaultProxyConfig() != null)
            {
301 302 303 304 305 306 307
                string host = null, token = null;
                try
                {
                    host = ((App)App.Current).PushChannelUri.Host;
                    token = ((App)App.Current).PushChannelUri.AbsolutePath;
                }
                catch { }
308 309 310 311 312 313 314

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

315 316 317 318
                if (Customs.AddPasswordInContactsParams)
                {
                    SIPAccountSettingsManager sip = new SIPAccountSettingsManager();
                    sip.Load();
319
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("pwd=" + sip.Password + ";app-id=" + host + ";pn-type=wp;pn-tok=" + token);
320 321 322 323 324
                }
                else
                {
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("app-id=" + host + ";pn-type=wp;pn-tok=" + token);
                }
325 326 327
            }
        }

328 329 330 331 332
        /// <summary>
        /// Configures the Logger
        /// </summary>
        public void ConfigureLogger()
        {
Ghislain MARY's avatar
Ghislain MARY committed
333 334
            // 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, "");
335
            // Else output the debug traces to a file
336 337
            ApplicationSettingsManager appSettings = new ApplicationSettingsManager();
            appSettings.Load();
338
            server.BackgroundModeLogger.Configure(appSettings.DebugEnabled, appSettings.LogDestination, appSettings.LogOption);
Ghislain MARY's avatar
Ghislain MARY committed
339
            server.LinphoneCoreFactory.OutputTraceListener = server.BackgroundModeLogger;
340
            server.LinphoneCoreFactory.SetLogLevel(appSettings.LogLevel);
Ghislain MARY's avatar
Ghislain MARY committed
341
            Logger.Instance.TraceListener = server.BackgroundModeLogger;
342
        }
343 344 345 346 347 348 349 350 351 352 353 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

        /// <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);
        }
396
        #endregion
397

398
        #region CallLogs
Sylvain Berfini's avatar
Sylvain Berfini committed
399
        private List<CallLog> _history;
400

401 402 403 404 405 406 407 408 409 410
        /// <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)
                {
411
                    return log.GetTo().AsStringUriOnly();
412 413 414 415 416
                }
            }
            return null;
        }

Sylvain Berfini's avatar
Sylvain Berfini committed
417
        /// <summary>
418
        /// Get the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
419 420
        /// </summary>
        /// <returns>A list of CallLogs, each one representing a type of calls (All, Missed, ...)</returns>
Sylvain Berfini's avatar
Sylvain Berfini committed
421
        public List<CallLog> GetCallsHistory()
Sylvain Berfini's avatar
Sylvain Berfini committed
422
        {
Sylvain Berfini's avatar
Sylvain Berfini committed
423
            _history = new List<CallLog>();
Sylvain Berfini's avatar
Sylvain Berfini committed
424

425 426 427 428 429
            if (LinphoneCore.GetCallLogs() != null)
            {
                foreach (LinphoneCallLog log in LinphoneCore.GetCallLogs())
                {
                    string from = log.GetFrom().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
430
                    if (from.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
431
                        from = log.GetFrom().AsStringUriOnly().Replace("sip:", "");
432 433

                    string to = log.GetTo().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
434
                    if (to.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
435
                        to = log.GetTo().AsStringUriOnly().Replace("sip:", "");
436 437

                    bool isMissed = log.GetStatus() == LinphoneCallStatus.Missed;
Sylvain Berfini's avatar
Sylvain Berfini committed
438 439 440
                    long startDate = log.GetStartDate();
                    CallLog callLog = new CallLog(log, from, to, log.GetDirection() == CallDirection.Incoming, isMissed, startDate);
                    _history.Add(callLog);
441 442 443
                }
            }

Sylvain Berfini's avatar
Sylvain Berfini committed
444 445 446 447
            return _history;
        }

        /// <summary>
448
        /// Remove one or many entries from the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
449
        /// </summary>
450
        /// <param name="logsToRemove">A list of CallLog to remove from history</param>
Sylvain Berfini's avatar
Sylvain Berfini committed
451
        /// <returns>A list of CallLogs, without the removed entries</returns>
452
        public void RemoveCallLogs(IEnumerable<CallLog> logsToRemove)
Sylvain Berfini's avatar
Sylvain Berfini committed
453 454 455
        {
            // 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
456
            for (int i = 0; i < logsToRemove.Count(); i++)
Sylvain Berfini's avatar
Sylvain Berfini committed
457
            {
458
                CallLog logToRemove = logsToRemove.ElementAt(i);
459
                LinphoneCore.RemoveCallLog(logToRemove.NativeLog as LinphoneCallLog);
Sylvain Berfini's avatar
Sylvain Berfini committed
460 461 462 463
            }
        }

        /// <summary>
464
        /// Remove all calls' history from LinphoneCore.
Sylvain Berfini's avatar
Sylvain Berfini committed
465
        /// </summary>
466
        /// <returns>An empty list</returns>
467
        public void ClearCallLogs()
Sylvain Berfini's avatar
Sylvain Berfini committed
468
        {
469
            LinphoneCore.ClearCallLogs();
Sylvain Berfini's avatar
Sylvain Berfini committed
470
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
471
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
472

Sylvain Berfini's avatar
Sylvain Berfini committed
473
        #region Call Management
Sylvain Berfini's avatar
Sylvain Berfini committed
474
        /// <summary>
475
        /// Start a new call to a sip address.
Sylvain Berfini's avatar
Sylvain Berfini committed
476 477 478 479
        /// </summary>
        /// <param name="sipAddress">SIP address to call</param>
        public void NewOutgoingCall(String sipAddress)
        {
480
            LinphoneCall LCall = LinphoneCore.Invite(sipAddress);
Sylvain Berfini's avatar
Sylvain Berfini committed
481
        }
482 483

        /// <summary>
484
        /// Stops the current call if any.
485 486 487
        /// </summary>
        public void EndCurrentCall()
        {
488 489
            LinphoneCall call = LinphoneCore.GetCurrentCall();
            if (call != null)
490
            {
491
                LinphoneCore.TerminateCall(call);
492
            }
493 494 495 496 497 498 499 500 501 502
            else
            {
                foreach (LinphoneCall lCall in LinphoneCore.GetCalls())
                {
                    if (lCall.GetState() == LinphoneCallState.Paused)
                    {
                        LinphoneCore.TerminateCall(lCall);
                    }
                }
            }
503 504
        }

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
        /// <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)
        {
526
            Logger.Msg("[LinphoneManager] Unmute requested");
527 528 529 530 531
            MuteMic(true);
        }

        private void MuteRequested(VoipCallCoordinator sender, MuteChangeEventArgs args)
        {
532
            Logger.Msg("[LinphoneManager] Mute requested");
533 534 535
            MuteMic(false);
        }

536
        /// <summary>
537
        /// Pauses the current call if any and if it's running.
538 539 540
        /// </summary>
        public void PauseCurrentCall()
        {
541 542 543 544 545
            if (LinphoneCore.GetCallsNb() > 0)
            {
                LinphoneCall call = LinphoneCore.GetCurrentCall();
                LinphoneCore.PauseCall(call);
            }
546 547 548
        }

        /// <summary>
549
        /// Resume the current call if any and if it's paused.
550 551 552
        /// </summary>
        public void ResumeCurrentCall()
        {
553 554 555 556 557
            foreach (LinphoneCall call in LinphoneCore.GetCalls()) {
                if (call.GetState() == LinphoneCallState.Paused)
                {
                    LinphoneCore.ResumeCall(call);
                }
558
            }
559
        }
560 561 562

        private void CallResumeRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
563
            Logger.Msg("[LinphoneManager] Resume requested");
564 565 566 567 568
            ResumeCurrentCall();
        }

        private void CallHoldRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
569
            Logger.Msg("[LinphoneManager] Pause requested");
570 571
            PauseCurrentCall();
        }
572
        #endregion
573

Ghislain MARY's avatar
Ghislain MARY committed
574 575 576
        #region Audio route handling
        private void AudioEndpointChanged(AudioRoutingManager sender, object args)
        {
Ghislain MARY's avatar
Ghislain MARY committed
577
            Logger.Msg("[LinphoneManager] AudioEndpointChanged:" + sender.GetAudioEndpoint().ToString());
Ghislain MARY's avatar
Ghislain MARY committed
578 579
        }

580 581 582 583
        /// <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
584 585 586 587 588 589 590 591 592 593 594 595 596
        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
597 598
        #region Video handling

Ghislain MARY's avatar
Ghislain MARY committed
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
        /// <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
624 625 626 627 628 629 630 631 632 633 634 635 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
        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
689
                    LinphoneCore.UpdateCall(call, null);
Ghislain MARY's avatar
Ghislain MARY committed
690 691 692 693 694
                }
            }
        }
        #endregion

695
        #region LinphoneCoreListener Callbacks
696 697 698
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
699 700
        public void AuthInfoRequested(string realm, string username)
        {
Ghislain MARY's avatar
Ghislain MARY committed
701
            Logger.Msg("[LinphoneManager] Auth info requested: realm=" + realm + ", username=" + username);
702 703
        }

704 705 706
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
707 708
        public void GlobalState(GlobalState state, string message)
        {
Ghislain MARY's avatar
Ghislain MARY committed
709
            Logger.Msg("[LinphoneManager] Global state changed: " + state.ToString() + ", message=" + message);
710 711
        }

712 713 714
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
715 716
        public void CallState(LinphoneCall call, LinphoneCallState state)
        {
Ghislain MARY's avatar
Ghislain MARY committed
717
            string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
718

Ghislain MARY's avatar
Ghislain MARY committed
719 720 721 722 723
            Logger.Msg("[LinphoneManager] Call state changed: " + sipAddress + " => " + state.ToString());
            if (state == LinphoneCallState.OutgoingProgress)
            {
                Logger.Msg("[LinphoneManager] Outgoing progress");
                BaseModel.UIDispatcher.BeginInvoke(() =>
724
                {
725
                    LookupForContact(call);
726

727
                    if (CallListener != null)
728
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
729 730 731 732 733 734
                });
            }
            else if (state == LinphoneCallState.IncomingReceived)
            {
                Logger.Msg("[LinphoneManager] Incoming received"); 
                BaseModel.UIDispatcher.BeginInvoke(() =>
735
                {
736
                    if (false) //TODO: Find a proper way to let the user choose between the two.
737 738 739 740 741
                    {
                        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();
                    }
742

743
                    LookupForContact(call);
Ghislain MARY's avatar
Ghislain MARY committed
744 745 746 747 748 749
                });
            }
            else if (state == LinphoneCallState.Connected)
            {
                Logger.Msg("[LinphoneManager] Connected");
                BaseModel.UIDispatcher.BeginInvoke(() =>
750
                {
751 752
                    if (CallListener != null)
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
753 754 755 756 757 758
                });
            }
            else if (state == LinphoneCallState.CallEnd || state == LinphoneCallState.Error)
            {
                Logger.Msg("[LinphoneManager] Call ended");
                BaseModel.UIDispatcher.BeginInvoke(() =>
759
                {
760
                    if (CallListener != null)
761
                        CallListener.CallEnded(call);
Ghislain MARY's avatar
Ghislain MARY committed
762 763 764 765 766 767
                });
            }
            else if (state == LinphoneCallState.Paused || state == LinphoneCallState.PausedByRemote)
            {
                Logger.Msg("[LinphoneManager] Call paused");
                BaseModel.UIDispatcher.BeginInvoke(() =>
768 769
                {
                    if (CallListener != null)
770
                        CallListener.PauseStateChanged(call, true);
Ghislain MARY's avatar
Ghislain MARY committed
771 772 773 774 775 776
                });
            }
            else if (state == LinphoneCallState.StreamsRunning)
            {
                Logger.Msg("[LinphoneManager] Call running");
                BaseModel.UIDispatcher.BeginInvoke(() =>
777 778
                {
                    if (CallListener != null)
779
                        CallListener.PauseStateChanged(call, false);
Ghislain MARY's avatar
Ghislain MARY committed
780 781 782 783 784 785
                });
            }
            else if (state == LinphoneCallState.Released)
            {
                Logger.Msg("[LinphoneManager] Call released");
                BaseModel.UIDispatcher.BeginInvoke(() =>
786
                {
787 788
                    //Update tile
                    UpdateLiveTile();
Ghislain MARY's avatar
Ghislain MARY committed
789 790
                });
            }
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
            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);
                });
            }
808 809
        }

810 811 812
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
813 814
        public void RegistrationState(LinphoneProxyConfig config, RegistrationState state, string message)
        {
815 816 817
            if (config == null)
                return;

818 819
            BaseModel.UIDispatcher.BeginInvoke(() =>
            {
820 821 822 823 824 825 826
                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);
                }
827
                catch { }
828
            });
829 830
        }

831 832 833
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
834
        public void DTMFReceived(LinphoneCall call, Char dtmf)
835 836 837 838
        {

        }

839 840 841
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
842
        public void EcCalibrationStatus(EcCalibratorStatus status, int delayMs)
843
        {
844 845 846 847 848 849 850 851 852
            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);
            }
853 854
        }

855 856 857
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
858 859 860 861 862
        public void CallEncryptionChanged(LinphoneCall call, bool encrypted, string authenticationToken)
        {

        }

863 864 865
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
866 867 868 869
        public void CallStatsUpdated(LinphoneCall call, LinphoneCallStats stats)
        {

        }
870

871 872 873 874 875
        /// <summary>
        /// Listener to let a view to be notified by LinphoneManager when a new message arrives.
        /// </summary>
        public MessageReceivedListener MessageListener { get; set; }

876 877 878
        /// <summary>
        /// Custom message box to display incoming messages when not in chat view
        /// </summary>
879
        public CustomMessageBox MessageReceivedNotification { get; set; }
880

881 882 883 884 885
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
        public void MessageReceived(LinphoneChatMessage message)
        {
886 887
            string sipAddress = message.GetFrom().AsStringUriOnly().Replace("sip:", "");
            Logger.Msg("[LinphoneManager] Message received from " + sipAddress + ": " + message.GetText());
888

889
            //Vibrate
890 891 892 893 894 895 896
            ChatSettingsManager settings = new ChatSettingsManager();
            settings.Load();
            if ((bool)settings.VibrateOnIncomingMessage)
            {
                VibrationDevice vibrator = VibrationDevice.GetDefault();
                vibrator.Vibrate(TimeSpan.FromSeconds(1));
            }
897

898
            if (MessageListener != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation() != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation().Equals(sipAddress))
899 900 901
            {
                MessageListener.MessageReceived(message);
            }
902 903
            else
            {
904 905 906 907 908 909 910
                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);
911

912 913 914
                    //TODO: Temp hack to remove
                    string url = message.GetExternalBodyUrl();
                    url = url.Replace("\"", "");
915

916 917 918
                    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);
919

920
                    //Displays the message as a popup
921 922
                    if (MessageReceivedNotification != null)
                    {
923
                        MessageReceivedNotification.Dismiss();
924 925
                    }

926
                    MessageReceivedNotification = new CustomMessageBox()
927
                    {
928 929 930 931
                        Caption = url.Length > 0 ? AppResources.ImageMessageReceived : AppResources.MessageReceived,
                        Message = url.Length > 0 ? "" : message.GetText(),
                        LeftButtonContent = AppResources.Show,
                        RightButtonContent = AppResources.Close
932
                    };
933 934 935 936 937 938 939 940 941 942 943 944

                    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();
945 946 947

                    //Update tile
                    UpdateLiveTile();
948
                });
949
            }
950
        }
951
        #endregion
952

953 954 955 956 957 958 959 960 961 962
        /// <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);
        }

963 964 965 966 967 968 969 970 971
        #region Contact Lookup
        private ContactManager ContactManager
        {
            get
            {
                return ContactManager.Instance;
            }
        }

972
        private void LookupForContact(LinphoneCall call)
973
        {
974
            try
975
            {
976 977
                string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
                if (call.GetRemoteAddress().GetDisplayName().Length == 0)
978
                {
979 980 981 982 983
                    if (sipAddress.StartsWith("sip:"))
                    {
                        sipAddress = sipAddress.Substring(4);
                    }
                    Logger.Msg("[LinphoneManager] Display name null, looking for remote address in contact: " + sipAddress);
984

985 986 987 988 989 990 991
                    ContactManager.ContactFound += OnContactFound;
                    ContactManager.FindContact(sipAddress);
                }
                else
                {
                    Logger.Msg("[LinphoneManager] Display name found: " + call.GetRemoteAddress().GetDisplayName());
                }
992
            }
993
            catch 
994
            {
995
                Logger.Warn("[LinphoneManager] Execption occured while looking for contact...");
996 997 998 999 1000 1001 1002 1003
            }
        }

        /// <summary>
        /// Callback called when the search on a phone number for a contact has a match
        /// </summary>
        private void OnContactFound(object sender, ContactFoundEventArgs e)
        {
1004
            if (e.ContactFound != null)
1005
            {
1006 1007 1008 1009 1010 1011 1012 1013
                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);
                }
1014 1015 1016
            }
        }
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
1017 1018
    }
}