LinphoneManager.cs 37.2 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 42 43
                if (lastNetworkState)
                {
                    NetworkSettingsManager.ConfigureTunnel();
                }
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 222
        public void DisconnectBackgroundProcessFromInterface()
        {
            if (!BackgroundProcessConnected)
            {
                Debug.WriteLine("[LinphoneManager] Background process not connected to interface yet");
                return;
            }

            BackgroundProcessConnected = false;
223
            isLinphoneRunning = false;
224
            Debug.WriteLine("[LinphoneManager] Background process disconnected from interface");
225 226

            // From this point onwards, it is no longer safe to use any objects in the background process, 
227
            // or for the background process to call back into this process.
228
            server = null;
229 230

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

234 235
            uiDisconnectedEvent.Set();
            uiDisconnectedEvent.Dispose();
236 237
            uiDisconnectedEvent = null;
        }
238
        #endregion
239

240
        #region Linphone Core init
241
        /// <summary>
242
        /// Creates a new LinphoneCore (if not created yet) using a LinphoneCoreFactory.
243
        /// </summary>
244
        public async Task InitLinphoneCore()
245
        {
Ghislain MARY's avatar
Ghislain MARY committed
246
            if ((server.LinphoneCoreFactory != null) && (server.LinphoneCore != null))
247 248
            {
                // Reconnect the listeners when coming back from background mode
249
                Debug.WriteLine("[LinphoneManager] LinphoneCore already created, skipping");
250

251
                server.LinphoneCore.CoreListener = this;
252
                isLinphoneRunning = true;
253
                // Set user-agent because it is not set if coming back from background mode
254
                server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
255
                return;
256
            }
257

258
            Debug.WriteLine("[LinphoneManager] Creating LinphoneCore");
259
            await SettingsManager.InstallConfigFile();
260
            LpConfig config = server.LinphoneCoreFactory.CreateLpConfig(SettingsManager.GetConfigPath(), SettingsManager.GetFactoryConfigPath());
261
            ConfigureLogger();
262
            server.LinphoneCoreFactory.CreateLinphoneCore(this, config);
Ghislain MARY's avatar
Ghislain MARY committed
263
            server.LinphoneCore.SetRootCA("Assets/rootca.pem");
264
            Debug.WriteLine("[LinphoneManager] LinphoneCore created");
265

266 267 268
            AudioRoutingManager.GetDefault().AudioEndpointChanged += AudioEndpointChanged;
            CallController.MuteRequested += MuteRequested;
            CallController.UnmuteRequested += UnmuteRequested;
269

270
            if (server.LinphoneCore.IsVideoSupported())
Sylvain Berfini's avatar
Sylvain Berfini committed
271
            {
272
                DetectCameras();
Sylvain Berfini's avatar
Sylvain Berfini committed
273
            }
274

275
            server.LinphoneCore.SetUserAgent(Customs.UserAgent, XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value);
276
            AddPushInformationsToContactParams();
277

Sylvain Berfini's avatar
Sylvain Berfini committed
278
            lastNetworkState = DeviceNetworkInformation.IsNetworkAvailable;
279
            server.LinphoneCore.SetNetworkReachable(lastNetworkState);
Sylvain Berfini's avatar
Sylvain Berfini committed
280
            DeviceNetworkInformation.NetworkAvailabilityChanged += new EventHandler<NetworkNotificationEventArgs>(OnNetworkStatusChanged);
281 282

            isLinphoneRunning = true;
283 284
        }

285 286 287 288 289 290 291
        /// <summary>
        /// Sets the push notif infos into proxy config contacts params
        /// </summary>
        public void AddPushInformationsToContactParams()
        {
            if (server.LinphoneCore.GetDefaultProxyConfig() != null)
            {
292 293 294 295 296 297 298
                string host = null, token = null;
                try
                {
                    host = ((App)App.Current).PushChannelUri.Host;
                    token = ((App)App.Current).PushChannelUri.AbsolutePath;
                }
                catch { }
299 300 301 302 303 304 305

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

306 307 308 309
                if (Customs.AddPasswordInContactsParams)
                {
                    SIPAccountSettingsManager sip = new SIPAccountSettingsManager();
                    sip.Load();
310
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("pwd=" + sip.Password + ";app-id=" + host + ";pn-type=wp;pn-tok=" + token);
311 312 313 314 315
                }
                else
                {
                    server.LinphoneCore.GetDefaultProxyConfig().SetContactParameters("app-id=" + host + ";pn-type=wp;pn-tok=" + token);
                }
316 317 318
            }
        }

319 320 321 322 323
        /// <summary>
        /// Configures the Logger
        /// </summary>
        public void ConfigureLogger()
        {
Ghislain MARY's avatar
Ghislain MARY committed
324 325
            // 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, "");
326
            // Else output the debug traces to a file
327 328
            ApplicationSettingsManager appSettings = new ApplicationSettingsManager();
            appSettings.Load();
329
            server.BackgroundModeLogger.Configure(appSettings.DebugEnabled, appSettings.LogDestination, appSettings.LogOption);
Ghislain MARY's avatar
Ghislain MARY committed
330
            server.LinphoneCoreFactory.OutputTraceListener = server.BackgroundModeLogger;
331
            server.LinphoneCoreFactory.SetLogLevel(appSettings.LogLevel);
Ghislain MARY's avatar
Ghislain MARY committed
332
            Logger.Instance.TraceListener = server.BackgroundModeLogger;
333
        }
334
        #endregion
335

336
        #region CallLogs
Sylvain Berfini's avatar
Sylvain Berfini committed
337
        private List<CallLog> _history;
338

339 340 341 342 343 344 345 346 347 348
        /// <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)
                {
349
                    return log.GetTo().AsStringUriOnly();
350 351 352 353 354
                }
            }
            return null;
        }

Sylvain Berfini's avatar
Sylvain Berfini committed
355
        /// <summary>
356
        /// Get the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
357 358
        /// </summary>
        /// <returns>A list of CallLogs, each one representing a type of calls (All, Missed, ...)</returns>
Sylvain Berfini's avatar
Sylvain Berfini committed
359
        public List<CallLog> GetCallsHistory()
Sylvain Berfini's avatar
Sylvain Berfini committed
360
        {
Sylvain Berfini's avatar
Sylvain Berfini committed
361
            _history = new List<CallLog>();
Sylvain Berfini's avatar
Sylvain Berfini committed
362

363 364 365 366 367
            if (LinphoneCore.GetCallLogs() != null)
            {
                foreach (LinphoneCallLog log in LinphoneCore.GetCallLogs())
                {
                    string from = log.GetFrom().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
368
                    if (from.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
369
                        from = log.GetFrom().AsStringUriOnly().Replace("sip:", "");
370 371

                    string to = log.GetTo().GetDisplayName();
Sylvain Berfini's avatar
Sylvain Berfini committed
372
                    if (to.Length == 0)
Sylvain Berfini's avatar
Sylvain Berfini committed
373
                        to = log.GetTo().AsStringUriOnly().Replace("sip:", "");
374 375

                    bool isMissed = log.GetStatus() == LinphoneCallStatus.Missed;
Sylvain Berfini's avatar
Sylvain Berfini committed
376 377 378
                    long startDate = log.GetStartDate();
                    CallLog callLog = new CallLog(log, from, to, log.GetDirection() == CallDirection.Incoming, isMissed, startDate);
                    _history.Add(callLog);
379 380 381
                }
            }

Sylvain Berfini's avatar
Sylvain Berfini committed
382 383 384 385
            return _history;
        }

        /// <summary>
386
        /// Remove one or many entries from the calls' history.
Sylvain Berfini's avatar
Sylvain Berfini committed
387
        /// </summary>
388
        /// <param name="logsToRemove">A list of CallLog to remove from history</param>
Sylvain Berfini's avatar
Sylvain Berfini committed
389
        /// <returns>A list of CallLogs, without the removed entries</returns>
390
        public void RemoveCallLogs(IEnumerable<CallLog> logsToRemove)
Sylvain Berfini's avatar
Sylvain Berfini committed
391 392 393
        {
            // 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
394
            for (int i = 0; i < logsToRemove.Count(); i++)
Sylvain Berfini's avatar
Sylvain Berfini committed
395
            {
396
                CallLog logToRemove = logsToRemove.ElementAt(i);
397
                LinphoneCore.RemoveCallLog(logToRemove.NativeLog as LinphoneCallLog);
Sylvain Berfini's avatar
Sylvain Berfini committed
398 399 400 401
            }
        }

        /// <summary>
402
        /// Remove all calls' history from LinphoneCore.
Sylvain Berfini's avatar
Sylvain Berfini committed
403
        /// </summary>
404
        /// <returns>An empty list</returns>
405
        public void ClearCallLogs()
Sylvain Berfini's avatar
Sylvain Berfini committed
406
        {
407
            LinphoneCore.ClearCallLogs();
Sylvain Berfini's avatar
Sylvain Berfini committed
408
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
409
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
410

Sylvain Berfini's avatar
Sylvain Berfini committed
411
        #region Call Management
Sylvain Berfini's avatar
Sylvain Berfini committed
412
        /// <summary>
413
        /// Start a new call to a sip address.
Sylvain Berfini's avatar
Sylvain Berfini committed
414 415 416 417
        /// </summary>
        /// <param name="sipAddress">SIP address to call</param>
        public void NewOutgoingCall(String sipAddress)
        {
418
            LinphoneCall LCall = LinphoneCore.Invite(sipAddress);
Sylvain Berfini's avatar
Sylvain Berfini committed
419
        }
420 421

        /// <summary>
422
        /// Stops the current call if any.
423 424 425
        /// </summary>
        public void EndCurrentCall()
        {
426 427
            LinphoneCall call = LinphoneCore.GetCurrentCall();
            if (call != null)
428
            {
429
                LinphoneCore.TerminateCall(call);
430
            }
431 432 433 434 435 436 437 438 439 440
            else
            {
                foreach (LinphoneCall lCall in LinphoneCore.GetCalls())
                {
                    if (lCall.GetState() == LinphoneCallState.Paused)
                    {
                        LinphoneCore.TerminateCall(lCall);
                    }
                }
            }
441 442
        }

443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
        /// <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)
        {
464
            Logger.Msg("[LinphoneManager] Unmute requested");
465 466 467 468 469
            MuteMic(true);
        }

        private void MuteRequested(VoipCallCoordinator sender, MuteChangeEventArgs args)
        {
470
            Logger.Msg("[LinphoneManager] Mute requested");
471 472 473
            MuteMic(false);
        }

474
        /// <summary>
475
        /// Pauses the current call if any and if it's running.
476 477 478
        /// </summary>
        public void PauseCurrentCall()
        {
479 480 481 482 483
            if (LinphoneCore.GetCallsNb() > 0)
            {
                LinphoneCall call = LinphoneCore.GetCurrentCall();
                LinphoneCore.PauseCall(call);
            }
484 485 486
        }

        /// <summary>
487
        /// Resume the current call if any and if it's paused.
488 489 490
        /// </summary>
        public void ResumeCurrentCall()
        {
491 492 493 494 495
            foreach (LinphoneCall call in LinphoneCore.GetCalls()) {
                if (call.GetState() == LinphoneCallState.Paused)
                {
                    LinphoneCore.ResumeCall(call);
                }
496
            }
497
        }
498 499 500

        private void CallResumeRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
501
            Logger.Msg("[LinphoneManager] Resume requested");
502 503 504 505 506
            ResumeCurrentCall();
        }

        private void CallHoldRequested(VoipPhoneCall sender, CallStateChangeEventArgs args)
        {
507
            Logger.Msg("[LinphoneManager] Pause requested");
508 509
            PauseCurrentCall();
        }
510
        #endregion
511

Ghislain MARY's avatar
Ghislain MARY committed
512 513 514
        #region Audio route handling
        private void AudioEndpointChanged(AudioRoutingManager sender, object args)
        {
Ghislain MARY's avatar
Ghislain MARY committed
515
            Logger.Msg("[LinphoneManager] AudioEndpointChanged:" + sender.GetAudioEndpoint().ToString());
Ghislain MARY's avatar
Ghislain MARY committed
516 517
        }

518 519 520 521
        /// <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
522 523 524 525 526 527 528 529 530 531 532 533 534
        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
535 536
        #region Video handling

Ghislain MARY's avatar
Ghislain MARY committed
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
        /// <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
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 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 624 625 626
        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
627
                    LinphoneCore.UpdateCall(call, null);
Ghislain MARY's avatar
Ghislain MARY committed
628 629 630 631 632
                }
            }
        }
        #endregion

633
        #region LinphoneCoreListener Callbacks
634 635 636
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
637 638
        public void AuthInfoRequested(string realm, string username)
        {
Ghislain MARY's avatar
Ghislain MARY committed
639
            Logger.Msg("[LinphoneManager] Auth info requested: realm=" + realm + ", username=" + username);
640 641
        }

642 643 644
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
645 646
        public void GlobalState(GlobalState state, string message)
        {
Ghislain MARY's avatar
Ghislain MARY committed
647
            Logger.Msg("[LinphoneManager] Global state changed: " + state.ToString() + ", message=" + message);
648 649
        }

650 651 652
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
653 654
        public void CallState(LinphoneCall call, LinphoneCallState state)
        {
Ghislain MARY's avatar
Ghislain MARY committed
655
            string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
656

Ghislain MARY's avatar
Ghislain MARY committed
657 658 659 660 661
            Logger.Msg("[LinphoneManager] Call state changed: " + sipAddress + " => " + state.ToString());
            if (state == LinphoneCallState.OutgoingProgress)
            {
                Logger.Msg("[LinphoneManager] Outgoing progress");
                BaseModel.UIDispatcher.BeginInvoke(() =>
662
                {
663
                    LookupForContact(call);
664

665
                    if (CallListener != null)
666
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
667 668 669 670 671 672
                });
            }
            else if (state == LinphoneCallState.IncomingReceived)
            {
                Logger.Msg("[LinphoneManager] Incoming received"); 
                BaseModel.UIDispatcher.BeginInvoke(() =>
673
                {
674
                    if (false) //TODO: Find a proper way to let the user choose between the two.
675 676 677 678 679
                    {
                        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();
                    }
680

681
                    LookupForContact(call);
Ghislain MARY's avatar
Ghislain MARY committed
682 683 684 685 686 687
                });
            }
            else if (state == LinphoneCallState.Connected)
            {
                Logger.Msg("[LinphoneManager] Connected");
                BaseModel.UIDispatcher.BeginInvoke(() =>
688
                {
689 690
                    if (CallListener != null)
                        CallListener.NewCallStarted(sipAddress);
Ghislain MARY's avatar
Ghislain MARY committed
691 692 693 694 695 696
                });
            }
            else if (state == LinphoneCallState.CallEnd || state == LinphoneCallState.Error)
            {
                Logger.Msg("[LinphoneManager] Call ended");
                BaseModel.UIDispatcher.BeginInvoke(() =>
697
                {
698
                    if (CallListener != null)
699
                        CallListener.CallEnded(call);
Ghislain MARY's avatar
Ghislain MARY committed
700 701 702 703 704 705
                });
            }
            else if (state == LinphoneCallState.Paused || state == LinphoneCallState.PausedByRemote)
            {
                Logger.Msg("[LinphoneManager] Call paused");
                BaseModel.UIDispatcher.BeginInvoke(() =>
706 707
                {
                    if (CallListener != null)
708
                        CallListener.PauseStateChanged(call, true);
Ghislain MARY's avatar
Ghislain MARY committed
709 710 711 712 713 714
                });
            }
            else if (state == LinphoneCallState.StreamsRunning)
            {
                Logger.Msg("[LinphoneManager] Call running");
                BaseModel.UIDispatcher.BeginInvoke(() =>
715 716
                {
                    if (CallListener != null)
717
                        CallListener.PauseStateChanged(call, false);
Ghislain MARY's avatar
Ghislain MARY committed
718 719 720 721 722 723
                });
            }
            else if (state == LinphoneCallState.Released)
            {
                Logger.Msg("[LinphoneManager] Call released");
                BaseModel.UIDispatcher.BeginInvoke(() =>
724
                {
725 726
                    //Update tile
                    UpdateLiveTile();
Ghislain MARY's avatar
Ghislain MARY committed
727 728
                });
            }
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
            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);
                });
            }
746 747
        }

748 749 750
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
751 752
        public void RegistrationState(LinphoneProxyConfig config, RegistrationState state, string message)
        {
753 754 755
            if (config == null)
                return;

756 757
            BaseModel.UIDispatcher.BeginInvoke(() =>
            {
758 759 760 761 762 763 764
                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);
                }
765
                catch { }
766
            });
767 768
        }

769 770 771
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
772
        public void DTMFReceived(LinphoneCall call, Char dtmf)
773 774 775 776
        {

        }

777 778 779
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
780
        public void EcCalibrationStatus(EcCalibratorStatus status, int delayMs)
781
        {
782 783 784 785 786 787 788 789 790
            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);
            }
791 792
        }

793 794 795
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
796 797 798 799 800
        public void CallEncryptionChanged(LinphoneCall call, bool encrypted, string authenticationToken)
        {

        }

801 802 803
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
804 805 806 807
        public void CallStatsUpdated(LinphoneCall call, LinphoneCallStats stats)
        {

        }
808

809 810 811 812 813
        /// <summary>
        /// Listener to let a view to be notified by LinphoneManager when a new message arrives.
        /// </summary>
        public MessageReceivedListener MessageListener { get; set; }

814 815 816
        /// <summary>
        /// Custom message box to display incoming messages when not in chat view
        /// </summary>
817
        public CustomMessageBox MessageReceivedNotification { get; set; }
818

819 820 821 822 823
        /// <summary>
        /// Callback for LinphoneCoreListener
        /// </summary>
        public void MessageReceived(LinphoneChatMessage message)
        {
824 825
            string sipAddress = message.GetFrom().AsStringUriOnly().Replace("sip:", "");
            Logger.Msg("[LinphoneManager] Message received from " + sipAddress + ": " + message.GetText());
826

827
            //Vibrate
828 829 830 831 832 833 834
            ChatSettingsManager settings = new ChatSettingsManager();
            settings.Load();
            if ((bool)settings.VibrateOnIncomingMessage)
            {
                VibrationDevice vibrator = VibrationDevice.GetDefault();
                vibrator.Vibrate(TimeSpan.FromSeconds(1));
            }
835

836
            if (MessageListener != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation() != null && MessageListener.GetSipAddressAssociatedWithDisplayConversation().Equals(sipAddress))
837 838 839
            {
                MessageListener.MessageReceived(message);
            }
840 841
            else
            {
842 843 844 845 846 847 848
                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);
849

850 851 852
                    //TODO: Temp hack to remove
                    string url = message.GetExternalBodyUrl();
                    url = url.Replace("\"", "");
853

854 855 856
                    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);
857

858
                    //Displays the message as a popup
859 860
                    if (MessageReceivedNotification != null)
                    {
861
                        MessageReceivedNotification.Dismiss();
862 863
                    }

864
                    MessageReceivedNotification = new CustomMessageBox()
865
                    {
866 867 868 869
                        Caption = url.Length > 0 ? AppResources.ImageMessageReceived : AppResources.MessageReceived,
                        Message = url.Length > 0 ? "" : message.GetText(),
                        LeftButtonContent = AppResources.Show,
                        RightButtonContent = AppResources.Close
870
                    };
871 872 873 874 875 876 877 878 879 880 881 882

                    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();
883 884 885

                    //Update tile
                    UpdateLiveTile();
886
                });
887
            }
888
        }
889
        #endregion
890

891 892 893 894 895 896 897 898 899 900
        /// <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);
        }

901 902 903 904 905 906 907 908 909
        #region Contact Lookup
        private ContactManager ContactManager
        {
            get
            {
                return ContactManager.Instance;
            }
        }

910
        private void LookupForContact(LinphoneCall call)
911
        {
912
            try
913
            {
914 915
                string sipAddress = call.GetRemoteAddress().AsStringUriOnly();
                if (call.GetRemoteAddress().GetDisplayName().Length == 0)
916
                {
917 918 919 920 921
                    if (sipAddress.StartsWith("sip:"))
                    {
                        sipAddress = sipAddress.Substring(4);
                    }
                    Logger.Msg("[LinphoneManager] Display name null, looking for remote address in contact: " + sipAddress);
922

923 924 925 926 927 928 929
                    ContactManager.ContactFound += OnContactFound;
                    ContactManager.FindContact(sipAddress);
                }
                else
                {
                    Logger.Msg("[LinphoneManager] Display name found: " + call.GetRemoteAddress().GetDisplayName());
                }
930
            }
931
            catch 
932
            {
933
                Logger.Warn("[LinphoneManager] Execption occured while looking for contact...");
934 935 936 937 938 939 940 941
            }
        }

        /// <summary>
        /// Callback called when the search on a phone number for a contact has a match
        /// </summary>
        private void OnContactFound(object sender, ContactFoundEventArgs e)
        {
942
            if (e.ContactFound != null)
943
            {
944 945 946 947 948 949 950 951
                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);
                }
952 953 954
            }
        }
        #endregion
Sylvain Berfini's avatar
Sylvain Berfini committed
955 956
    }
}