Initial Import
[profile/ivi/hfdialer.git] / src / pacontrol.cpp
1 /*
2  * hfdialer - Hands Free Voice Call Manager
3  * Copyright (c) 2012, Intel Corporation.
4  *
5  * This program is licensed under the terms and conditions of the
6  * Apache License, version 2.0.  The full text of the Apache License is at
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  */
10
11 #include "pacontrol.h"
12 #include "managerproxy.h"
13 #include "callmanager.h"
14 #include <string.h>
15 #include <QStringBuilder>
16 #include <QList>
17 #include <QDebug>
18
19 // Define our pulse audio loop and connection variables
20 static PAControl* paControl = new PAControl;
21 // Create a mainloop API and connection to the default server
22 static pa_glib_mainloop *pa_ml = NULL;
23
24 static void pa_subscribed_events_cb(pa_context *c, enum pa_subscription_event_type t, uint32_t , void *);
25
26 static void operation_callback(pa_context *c, int success, void *userdata) {
27     Q_UNUSED(c);
28     Q_UNUSED(userdata);
29     if (!success) {
30         qDebug() << QString("Operation Failed");
31         paControl->setErrorMsg(QString("Operation Failed"));
32     }
33 }
34
35 static void module_callback(pa_context *c, uint32_t index, void *userdata) {
36     Q_UNUSED(c);
37     Q_UNUSED(userdata);
38     if (index == PA_INVALID_INDEX) {
39         qDebug() << QString("Load module failed");
40         paControl->setErrorMsg(QString("Load module failed"));
41     }
42 }
43
44 static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int is_last, void *userdata) {
45     Q_UNUSED(c);
46     Q_UNUSED(userdata);
47     PADevice *source;
48
49     if (is_last > 0) {
50         //end of the list
51         paControl->release();
52         return;
53     }
54
55     //qDebug() << "pa_sourcelist_cb() Source added: " << l->name;
56     source = new PADevice();
57     source->index = l->index;
58     if(l->name != NULL)
59         source->name = l->name;
60     if(l->description != NULL)
61         source->description = l->description;
62     paControl->addSource(source);
63 }
64
65 static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int is_last, void *userdata) {
66     Q_UNUSED(c);
67     Q_UNUSED(userdata);
68     PADevice *sink;
69
70     if (is_last > 0) {
71         //end of the list
72         paControl->release();
73         return;
74     }
75
76     sink = new PADevice();
77     sink->index = l->index;
78     if(l->name != NULL)
79         sink->name = l->name;
80     if(l->description != NULL)
81         sink->description = l->description;
82     paControl->addSink(sink);
83 }
84
85 static void pa_modulelist_cb(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {
86     Q_UNUSED(c);
87     Q_UNUSED(userdata);
88     PAModule *module;
89
90     if (is_last > 0) {
91         //end of the list
92         paControl->release();
93         return;
94     }
95
96     module = new PAModule();
97     module->index = i->index;
98     if(i->name != NULL)
99         module->name = i->name;
100     if(i->argument != NULL)
101         module->argument = i->argument;
102     paControl->addModule(module);
103 }
104
105 static void pa_state_cb(pa_context *c, void *) {
106
107     pa_context_state_t state = pa_context_get_state(c);
108     if(state == PA_CONTEXT_READY)
109         {
110             paControl->setState(true);
111             pa_context_set_subscribe_callback(c, pa_subscribed_events_cb, NULL);
112             pa_operation *o;
113             if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
114                                            (PA_SUBSCRIPTION_MASK_MODULE|
115                                             PA_SUBSCRIPTION_MASK_SINK|
116                                             PA_SUBSCRIPTION_MASK_SOURCE|
117                                             PA_SUBSCRIPTION_MASK_SINK_INPUT|
118                                             PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
119                 qWarning("pa_context_subscribe() failed");
120             }
121             if(o) pa_operation_unref(o);
122             ///Get an initial list of sinks, sources and modules.
123             paControl->addRef();
124             pa_context_get_sink_info_list(c, pa_sinklist_cb, NULL);
125             paControl->addRef();
126             pa_context_get_source_info_list(c, pa_sourcelist_cb, NULL);
127             paControl->addRef();
128             pa_context_get_module_info_list(c, pa_modulelist_cb, NULL);
129         }
130     else if(state == PA_CONTEXT_FAILED)
131         {
132             ///Pulseaudio crashed?
133             paControl->setState(false);
134         }
135     else
136         {
137             qDebug()<<"PA state: "<<(int) state;
138         }
139 }
140
141 static void pa_subscribed_events_cb(pa_context *c, enum pa_subscription_event_type t, uint32_t , void *)
142 {
143     switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
144         {
145         case PA_SUBSCRIPTION_EVENT_SINK:
146             qDebug("PA_SUBSCRIPTION_EVENT_SINK event triggered");
147             foreach(PADevice* dev, paControl->sinkList)
148                 {
149                     delete dev;
150                 }
151             paControl->sinkList.clear();
152             paControl->addRef();
153             pa_context_get_sink_info_list(c, pa_sinklist_cb, NULL);
154             break;
155         case PA_SUBSCRIPTION_EVENT_SOURCE:
156             qDebug("PA_SUBSCRIPTION_EVENT_SOURCE event triggered");
157             foreach(PADevice* dev, paControl->sourceList)
158                 {
159                     delete dev;
160                 }
161             paControl->sourceList.clear();
162             paControl->addRef();
163             pa_context_get_source_info_list(c, pa_sourcelist_cb, NULL);
164             break;
165         case PA_SUBSCRIPTION_EVENT_MODULE:
166             qDebug("PA_SUBSCRIPTION_EVENT_MODULE event triggered");
167             foreach(PAModule* dev, paControl->moduleList)
168                 {
169                     delete dev;
170                 }
171             paControl->moduleList.clear();
172             paControl->addRef();
173             pa_context_get_module_info_list(c, pa_modulelist_cb, NULL);
174             break;
175         }
176 }
177
178 PAControl::PAControl(QObject *parent)
179     :QObject(parent) {
180     paControl = this;
181     status = SUCCESS;
182     m_paState=false;
183
184     paInit();
185 }
186
187 PAControl::~PAControl()
188 {
189     paCleanup();
190
191     foreach (PADevice *source, sourceList) {
192         qDebug() << QString("delete source");
193         delete source;
194     }
195     foreach (PADevice *sink, sinkList) {
196         qDebug() << QString("delete sink");
197         delete sink;
198     }
199     foreach (PAModule *module, moduleList) {
200         qDebug() << QString("delete module");
201         delete module;
202     }
203
204     qDebug() << QString("~PAControl()");
205     paControl = NULL;
206 }
207
208 PAControl* PAControl::instance()
209 {
210     return paControl;
211 }
212
213 void PAControl::paInit() {
214     qDebug() << "paInit()";
215
216     // Create a mainloop API and connection to the default server
217     if(!pa_ml)
218         pa_ml = pa_glib_mainloop_new(NULL);
219     pa_ctx = pa_context_new(
220                             pa_glib_mainloop_get_api(pa_ml),
221                             "com.tizen.hfp");
222
223     // This function connects to the pulse server
224     if (pa_context_connect(pa_ctx,
225                            NULL,
226                            PA_CONTEXT_NOFAIL, NULL) < 0)
227         {
228             qCritical("PulseAudioService: pa_context_connect() failed");
229             paCleanup();
230             return;
231         }
232
233     m_refCounter = 0;
234     m_connected = false;
235     m_audioRouted = false;
236     m_btSourceReady =false;
237     m_btSinkReady = false;
238
239     pa_context_set_state_callback(pa_ctx, pa_state_cb, NULL);
240 }
241
242 void PAControl::paCleanup() {
243     qDebug() << "paCleanup()";
244     if(pa_ctx)
245         pa_context_disconnect(pa_ctx);
246     if(pa_ctx)
247         pa_context_unref(pa_ctx);
248 }
249
250 void PAControl::setState(bool state)
251 {
252     m_paState = state;
253     if(state == false)
254         {
255             emit paFailed();
256         }
257 }
258
259 void PAControl::addRef()
260 {
261     m_refCounter++;
262 }
263
264 void PAControl::release()
265 {
266     m_refCounter--;
267     Q_ASSERT(m_refCounter >= 0);
268 }
269
270 void PAControl::reconnect() {
271     qDebug() << "Pulseaudio: reconnect()";
272     if(paControl)
273         delete paControl;
274     paControl = new PAControl();
275 }
276
277
278
279 PADevice* PAControl::findBluezSource() {
280
281     if (sourceList.size() == 0)
282         {
283             addRef();
284             pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, NULL);
285             if(pa_op) pa_operation_unref(pa_op);
286
287             return NULL;
288         }
289
290     foreach (PADevice *source, sourceList) {
291         QString name = source->name;
292
293         if (name.startsWith(QString("bluez_source."), Qt::CaseSensitive)) {
294             qDebug() << QString("   Matched Bluez source: ") << name;
295             return source;
296         }
297     }
298
299     qDebug() << QString("Bluez source: none found");
300     return NULL;
301 }
302
303 PADevice*  PAControl::findBluezSink() {
304
305     if (sinkList.size() == 0)
306         {
307             addRef();
308             pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, NULL);
309             if(pa_op) pa_operation_unref(pa_op);
310
311             return NULL;
312         }
313
314     foreach (PADevice *sink, sinkList) {
315         QString name = sink->name;
316
317         if (name.startsWith(QString("bluez_sink."), Qt::CaseSensitive)) {
318             qDebug() << QString("   Matched Bluez sink: ") << name;
319             return sink;
320         }
321     }
322
323     qDebug() << QString("Bluez sink: none found");
324     return NULL;
325 }
326
327 PADevice* PAControl::findAlsaSource(QString alsasource) {
328
329     if (sourceList.size() == 0)
330         {
331             addRef();
332             pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, NULL);
333             if(pa_op) pa_operation_unref(pa_op);
334
335             return NULL;
336         }
337
338     foreach (PADevice *source, sourceList) {
339         qDebug() << QString("Alsa source: ") << source->name;
340         QString name = source->name;
341
342         if (!alsasource.isNull() && !alsasource.isEmpty())
343             {
344                 // if alsa source name is provided
345                 if (alsasource == name)
346                     {
347                         qDebug() << QString("   Matched Alsa source: ") << name;
348                         return source;
349                     }
350             } else if (name.startsWith(QString("alsa_input."), Qt::CaseSensitive) &&
351                        name.endsWith(QString("analog-stereo"), Qt::CaseSensitive) &&
352                        !name.contains(QString("timb"))) {
353             // this is default behavior, it will try to look up one
354             qDebug() << QString("   Matched Alsa source: ") << name;
355             return source;
356         }
357     }
358
359     qDebug() << QString("Alsa source: none found");
360     return NULL;
361 }
362
363 PADevice*  PAControl::findAlsaSink(QString alsasink) {
364
365     if (sinkList.size() == 0)
366         {
367             addRef();
368             pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, NULL);
369             if(pa_op) pa_operation_unref(pa_op);
370
371             return NULL;
372         }
373
374     foreach (PADevice *sink, sinkList) {
375         qDebug() << QString("Alsa sink: ") << sink->name;
376         QString name = sink->name;
377
378         if (!alsasink.isNull() && !alsasink.isEmpty())
379             {
380                 // if alsa sink name is provided
381                 if (alsasink == name)
382                     {
383                         qDebug() << QString("   Matched Alsa sink: ") << name;
384                         return sink;
385                     }
386             } else if (name.startsWith(QString("alsa_output."), Qt::CaseSensitive) &&
387                        name.endsWith(QString("analog-stereo"), Qt::CaseSensitive) &&
388                        !name.contains(QString("timb"))) {
389             // this is default behavior, it will try to look up one
390             qDebug() << QString("   Matched Alsa sink: ") << name;
391             return sink;
392         }
393     }
394
395     qDebug() << QString("Alsa sink: none found");
396     return NULL;
397 }
398
399 PAModule*  PAControl::findModule(QString name, QString pattern) {
400
401     if (moduleList.size() == 0)
402         {
403             addRef();
404             pa_op = pa_context_get_module_info_list(pa_ctx, pa_modulelist_cb, NULL);
405             if(pa_op) pa_operation_unref(pa_op);
406
407             return NULL;
408         }
409
410     foreach (PAModule *module, moduleList) {
411         if (module->name.contains(name) && module->argument.contains(pattern)) {
412             qDebug() << QString("   Matched module: ") << module->name;
413             qDebug() << QString("   argument: ") << module->argument;
414             return module;
415         }
416     }
417
418     qDebug() << QString("Module: none found");
419     return NULL;
420 }
421
422 QList<PAModule*> PAControl::getAllModules()
423 {
424     if (moduleList.size() == 0)
425         {
426             addRef();
427             pa_op = pa_context_get_module_info_list(pa_ctx, pa_modulelist_cb, NULL);
428             if(pa_op) pa_operation_unref(pa_op);
429         }
430
431     return moduleList;
432 }
433
434 void PAControl::addSource(PADevice* device)
435 {
436     foreach(PADevice* dev, sourceList)
437         {
438             if(dev->name == device->name)
439                 {
440                     delete device;
441                     return; /// already exists
442                 }
443         }
444
445     sourceList.append(device);
446     emit sourceAppeared(device);
447 }
448
449 void PAControl::addSink(PADevice* device)
450 {
451     foreach(PADevice* dev, sinkList)
452         {
453             if(dev->name == device->name)
454                 {
455                     delete device;
456                     return; /// already exists
457                 }
458         }
459
460     sinkList.append(device);
461     emit sinkAppeared(device);
462 }
463
464 void PAControl::addModule(PAModule *module)
465 {
466     foreach(PAModule* dev, moduleList)
467         {
468             if(dev->name == module->name && dev->index == module->index)
469                 {
470                     delete module;
471                     return; /// already exists
472                 }
473         }
474
475     moduleList.append(module);
476     emit moduleAppeared(module);
477 }
478
479 void PAControl::routeSourceWithSink(PADevice *source, PADevice *sink) {
480     qDebug() << "Routing from " << source->name << " to " << sink->name;
481
482     if (source != NULL && sink != NULL) {
483         QString arg = "source=\"" % source->name % "\" sink=\"" % sink->name % "\"";
484
485         pa_op = pa_context_load_module(pa_ctx, "module-loopback", arg.toAscii().data(), module_callback, NULL);
486         if(pa_op) pa_operation_unref(pa_op);
487
488         qDebug() << QString("load-module module-loopback ") << arg;        
489     }
490 }
491
492 void PAControl::toggleMuteSource(PADevice *source, bool isMute) {
493
494     if (source != NULL) {
495         pa_op =pa_context_set_source_mute_by_name(pa_ctx, source->name.toAscii().data(), isMute, operation_callback, NULL);
496         if(pa_op) pa_operation_unref(pa_op);
497
498         qDebug() << QString("set source mute ") << source->name << QString(" to ") << isMute;
499     }
500 }
501
502 void  PAControl::unloadModule(PAModule* module) {
503
504     if (module != NULL && module->index >= 0) {
505         pa_op = pa_context_unload_module(pa_ctx, module->index, operation_callback, NULL);
506         if(pa_op) pa_operation_unref(pa_op);
507         qDebug() << QString("unload-module module-loopback ") <<  QString(module->name) << QString(" at index ") << module->index;
508     }
509 }
510
511 PAStatus PAControl::getStatus() {
512     return this->status;
513 }
514
515 void PAControl::setErrorMsg(QString msg) {
516     if (msg != NULL)
517         {
518             this->status = ERROR;
519             this->errorMsg = msg;
520         }
521 }
522
523 QString PAControl::getErrorMsg() {
524     return this->errorMsg;
525 }
526
527 void PAControl::onSourceAppeared(PADevice* device) {
528
529     CallManager *cm = ManagerProxy::instance()->callManager();
530     if (!cm || !cm->isValid())
531         return;
532
533     if(cm->callCount() == 0)
534         {
535             qDebug() << "no calls active, ignore";
536             return;
537         }
538
539     if(device->name.contains("bluez_source"))
540         {
541             m_btSourceReady = true;
542         }
543
544     if(!m_audioRouted && m_btSourceReady && m_btSinkReady)
545         {
546             qDebug() << QString("Route microphone and speakers");
547             routeAudio();
548         }
549 }
550
551 void PAControl::onSinkAppeared(PADevice* device) {
552     CallManager *cm = ManagerProxy::instance()->callManager();
553     if (!cm || !cm->isValid())
554         return;
555
556     if(cm->callCount() == 0)
557         {
558             qDebug() << "no calls active, ignore";
559             return;
560         }
561
562     if((device)->name.contains("bluez_sink"))
563         {
564             m_btSinkReady = true;
565         }
566
567     if(!m_audioRouted && m_btSourceReady && m_btSinkReady)
568         {
569             qDebug() << QString("Route microphone and speakers");
570             routeAudio();
571         }
572 }
573
574 void PAControl::routeAudio()
575 {
576     PADevice* source;
577     PADevice* sink;
578     PADevice* mic;
579     PADevice* speaker;
580
581     if (m_audioRouted)
582         {
583             qDebug() << QString("Audio already routed");
584             return;
585         }
586
587     if (m_refCounter > 0)
588         {
589             qDebug() << "PA callback not finished, retry";
590             QTimer::singleShot(1000, this, SLOT(routeAudio()));
591             return;
592         }
593
594     qDebug() << QString("Route audio");
595     source = paControl->findBluezSource();
596     sink = paControl->findBluezSink();
597
598     if(source == NULL || sink == NULL) {
599         qDebug() << QString("Bluez source or speaker not found");
600         return;
601     }
602
603     QString alsaSourceName = MGConfItem(QString("/apps/dialer/alsasource")).value().toString();
604     QString alsaSinkName = MGConfItem(QString("/apps/dialer/alsasink")).value().toString();
605
606     mic = paControl->findAlsaSource(alsaSourceName);
607     speaker = paControl->findAlsaSink(alsaSinkName);
608
609     if (mic != NULL and speaker != NULL)
610         {
611             paControl->routeSourceWithSink(source, speaker);
612             paControl->routeSourceWithSink(mic, sink);
613             qDebug() << QString("Create loopback modules successful");
614         }
615     else {
616         qDebug() << QString("Alsa source and speaker not found");
617     }
618
619     m_audioRouted = true;
620     disconnect(this, SIGNAL(sourceAppeared(PADevice*)));
621     disconnect(this, SIGNAL(sinkAppeared(PADevice*)));
622 }
623
624 void PAControl::unrouteAudio()
625 {
626     qDebug() << QString("Unroute audio");
627     PAControl* paControl = PAControl::instance();
628
629     QList<PAModule*> mlist = paControl->getAllModules();
630     foreach(PAModule *module, mlist)
631         {
632             if (module->name.contains("module-loopback") &&
633                 module->argument.contains("bluez") &&
634                 module->argument.contains("alsa")) {
635                 qDebug() << QString("Found loopback module, index: ") << module->index;
636                 paControl->unloadModule(module);
637                 qDebug() << QString("Remove loopback module successful");
638             }
639         }
640
641     m_audioRouted = false;
642     m_btSourceReady = false;
643     m_btSinkReady = false;
644 }
645
646 void PAControl::onCallsChanged()
647 {
648     TRACE;
649
650     CallManager *cm = ManagerProxy::instance()->callManager();
651     if (!cm || !cm->isValid())
652         {
653         qDebug("no call manager.  Aborting");
654             return;
655         }
656
657     if (cm->dialingCall() || cm->activeCall() || cm->callCount() > 1)
658         {
659             // new call is dialing or phone is picked up
660             qDebug() << "PAControl: new call in progress";
661
662             if(m_audioRouted)
663                 {
664                     qDebug() << QString("Audio already routed");
665                     return;
666                 }
667
668             if(m_btSourceReady && m_btSinkReady)
669                 {
670                     qDebug() << QString("Route microphone and speakers");
671                     routeAudio();
672                 }
673             else
674                 {
675                     if(this->findBluezSource() != NULL && this->findBluezSink() != NULL)
676                         {
677                             // bt source and sink exists
678                             m_btSourceReady = true;
679                             m_btSinkReady = true;
680                             qDebug() << QString("Route microphone and speakers");
681                             routeAudio();
682                         }
683                     else
684                         {
685                             //no bt source or sink yet, let's wait until source and sink appears
686                             m_btSourceReady = false;
687                             m_btSinkReady = false;
688                             connect(this, SIGNAL(sourceAppeared(PADevice*)), this, SLOT(onSourceAppeared(PADevice*)));
689                             connect(this, SIGNAL(sinkAppeared(PADevice*)), this, SLOT(onSinkAppeared(PADevice*)));
690                             qDebug() << QString("Audio not routed yet, wait for bt source and sinks");
691                         }
692                 }
693         }
694     else if (cm->callCount() <= 0)
695         {
696             qDebug() << QString("no more ofono calls");
697             if(m_audioRouted)
698                 {
699                     qDebug() << QString("Unroute microphone and speakers");
700                     unrouteAudio();
701                 }
702         }
703 }
704
705 /* Local Variables:      */
706 /* mode:c++              */
707 /* c-basic-offset:4      */
708 /* indent-tabs-mode: nil */
709 /* End:                  */
710