Merge lp:~xavi-garcia-mena/indicator-sound/last-running-player-accounts-service into lp:indicator-sound/15.10

Proposed by Xavi Garcia
Status: Merged
Approved by: Charles Kerr
Approved revision: 537
Merged at revision: 530
Proposed branch: lp:~xavi-garcia-mena/indicator-sound/last-running-player-accounts-service
Merge into: lp:indicator-sound/15.10
Diff against target: 2481 lines (+1148/-895)
16 files modified
data/com.canonical.indicator.sound.gschema.xml (+2/-13)
src/CMakeLists.txt (+22/-11)
src/accounts-service-access.vala (+235/-0)
src/main.c (+63/-61)
src/service.vala (+15/-8)
src/sound-menu.vala (+35/-13)
src/volume-control-pulse.vala (+12/-141)
tests/integration/indicator-sound-test-base.cpp (+29/-5)
tests/integration/indicator-sound-test-base.h (+4/-0)
tests/integration/test-indicator.cpp (+69/-12)
tests/notifications-test.cc (+568/-558)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp (+15/-0)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h (+4/-0)
tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml (+1/-0)
tests/sound-menu.cc (+16/-16)
tests/volume-control-test.cc (+58/-57)
To merge this branch: bzr merge lp:~xavi-garcia-mena/indicator-sound/last-running-player-accounts-service
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+286897@code.launchpad.net

Commit message

This branch sets the last running player using accounts service instead of gsettings.
It also includes a new class AccountsServiceAccess, to centralize all accesses to account service properties.

Description of the change

This branch sets the last running player using accounts service instead of gsettings.
It also includes a new class AccountsServiceAccess, to centralize all accesses to account service properties.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
533. By Xavi Garcia

Fixed integration test

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
534. By Xavi Garcia

Removed log statement

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
535. By Xavi Garcia

Accounts service notification fix

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

A lot of this looks great. I like the improved tests and the refactoring of AccountsServices into its own class.

Several questions inline though, with a couple of serious issues eg the way GCancellable is used and my questions about the use of last_player_added in SoundMenu.

review: Needs Information
536. By Xavi Garcia

Changed following Charles's suggestions

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Thanks for the review, Charles!

I assumed the existing code had passed already a code review in deep and did not refactored it. Big mistake.

I've fixed almost everything you commented. I think there's only one point remaining... please take a look at the inline comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Ah, I didn't realize that GCancellable issue was a carryover from old code. I'm glad it's fixed.

One last suggestion, I do think AccountsServiceAccess should have a private GCancellable member that gets passed to all these bus calls + a call to cancellable.cancel() in the AccountsServiceAccess destructor. This won't affect general use since the object's lifespan continues for the life of the process, but it's useful to prevent issues when shutting down the bus in unit tests.

Nice fix wrt the cascading signal emission.

537. By Xavi Garcia

Added Cancellable to AccountsServiceAccess

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Good stuff.

I won't block this patch further because this is a preexisting condition, but if you want to finish making AccountsServiceAccess' DBus calls cancellable there are still a handful of noncancellable calls:

accounts-service-access.vala:133: yield DBusProxy.create_for_bus()
accounts-service-access.vala:141: yield accounts_proxy.call()
accounts-service-access.vala:145: yield DBusProxy.create_for_bus()
accounts-service-access.vala:158: yield _user_proxy.get_connection ().call()
accounts-service-access.vala:181: yield Bus.get_proxy()

In (I think) all of these cases the cancellable is passed in as the invisible last argument, where vala has a C++-like default argument which is null.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/com.canonical.indicator.sound.gschema.xml'
2--- data/com.canonical.indicator.sound.gschema.xml 2016-02-10 13:08:49 +0000
3+++ data/com.canonical.indicator.sound.gschema.xml 2016-03-03 08:59:58 +0000
4@@ -59,10 +59,10 @@
5 <description>
6 How long to remember a user's approval of the confirmation dialog discussed in the
7 description of 'warning-volume-enabled'.
8-
9+
10 The default value (72,000 seconds) corresponds to the 20 hours suggested by
11 EU standard EN 60950-1/Al2: “The acknowledgement does not need to be repeated
12- more than once every 20 h of cumulative listening time.”
13+ more than once every 20 h of cumulative listening time.”
14 </description>
15 </key>
16 <key name="warning-volume-decibels" type="d">
17@@ -102,16 +102,5 @@
18 </description>
19 </key>
20
21- <key name="last-running-player" type="s">
22- <default>""</default>
23- <summary>Stores which was the last running music player.</summary>
24- <description>
25- To make the last running player persistent and be able to set its playback controls
26- we store which was the last player running id.
27-
28- The default value ("") corresponds to no player.
29- </description>
30- </key>
31-
32 </schema>
33 </schemalist>
34
35=== modified file 'src/CMakeLists.txt'
36--- src/CMakeLists.txt 2016-02-12 19:36:10 +0000
37+++ src/CMakeLists.txt 2016-03-03 08:59:58 +0000
38@@ -69,29 +69,37 @@
39 media-player-list
40 mpris2-interfaces
41 accounts-service-user
42+ accounts-service-access
43 )
44 vala_add(indicator-sound-service
45 options.vala
46 DEPENDS
47 volume-control
48 volume-control-pulse
49+ accounts-service-access
50 )
51 vala_add(indicator-sound-service
52 options-gsettings.vala
53 DEPENDS
54 options
55- volume-control-pulse
56+ volume-control-pulse
57 volume-control
58+ accounts-service-access
59 )
60 vala_add(indicator-sound-service
61 volume-control.vala
62 DEPENDS
63 options
64- volume-control-pulse
65+ volume-control-pulse
66+ accounts-service-access
67+)
68+vala_add(indicator-sound-service
69+ accounts-service-access.vala
70 )
71 vala_add(indicator-sound-service
72 volume-control-pulse.vala
73 DEPENDS
74+ accounts-service-access
75 options
76 volume-control
77 )
78@@ -99,20 +107,22 @@
79 volume-warning.vala
80 DEPENDS
81 options
82- volume-control-pulse
83+ volume-control-pulse
84 volume-control
85 warn-notification
86- notification
87+ notification
88+ accounts-service-access
89 )
90 vala_add(indicator-sound-service
91 volume-warning-pulse.vala
92 DEPENDS
93 volume-warning
94- options
95- volume-control-pulse
96- volume-control
97- warn-notification
98- notification
99+ options
100+ volume-control-pulse
101+ volume-control
102+ warn-notification
103+ notification
104+ accounts-service-access
105 )
106 vala_add(indicator-sound-service
107 media-player.vala
108@@ -161,8 +171,9 @@
109 DEPENDS
110 media-player
111 volume-control
112- options
113- volume-control-pulse
114+ options
115+ volume-control-pulse
116+ accounts-service-access
117 )
118 vala_add(indicator-sound-service
119 accounts-service-user.vala
120
121=== added file 'src/accounts-service-access.vala'
122--- src/accounts-service-access.vala 1970-01-01 00:00:00 +0000
123+++ src/accounts-service-access.vala 2016-03-03 08:59:58 +0000
124@@ -0,0 +1,235 @@
125+/*
126+ * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*-
127+ * Copyright 2016 Canonical Ltd.
128+ *
129+ * This program is free software; you can redistribute it and/or modify
130+ * it under the terms of the GNU General Public License as published by
131+ * the Free Software Foundation; version 3.
132+ *
133+ * This program is distributed in the hope that it will be useful,
134+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
135+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
136+ * GNU General Public License for more details.
137+ *
138+ * You should have received a copy of the GNU General Public License
139+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
140+ *
141+ * Authors:
142+ * Xavi Garcia <xavi.garcia.mena@canonical.com>
143+ */
144+
145+using PulseAudio;
146+using Notify;
147+using Gee;
148+
149+[DBus (name="com.canonical.UnityGreeter.List")]
150+interface GreeterListInterfaceAccess : Object
151+{
152+ public abstract async string get_active_entry () throws IOError;
153+ public signal void entry_selected (string entry_name);
154+}
155+
156+public class AccountsServiceAccess : Object
157+{
158+ private DBusProxy _user_proxy;
159+ private GreeterListInterfaceAccess _greeter_proxy;
160+ private double _volume = 0.0;
161+ private string _last_running_player = "";
162+ private bool _mute = false;
163+ private Cancellable _dbus_call_cancellable;
164+
165+ public AccountsServiceAccess ()
166+ {
167+ _dbus_call_cancellable = new Cancellable ();
168+ setup_accountsservice.begin ();
169+ }
170+
171+ ~AccountsServiceAccess ()
172+ {
173+ _dbus_call_cancellable.cancel ();
174+ }
175+
176+ public string last_running_player
177+ {
178+ get
179+ {
180+ return _last_running_player;
181+ }
182+ set
183+ {
184+ sync_last_running_player_to_accountsservice.begin (value);
185+ }
186+ }
187+
188+ public bool mute
189+ {
190+ get
191+ {
192+ return _mute;
193+ }
194+ set
195+ {
196+ sync_mute_to_accountsservice.begin (value);
197+ }
198+ }
199+
200+ public double volume
201+ {
202+ get
203+ {
204+ return _volume;
205+ }
206+ set
207+ {
208+ sync_volume_to_accountsservice.begin (value);
209+ }
210+ }
211+
212+ /* AccountsService operations */
213+ private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties)
214+ {
215+ Variant volume_variant = changed_properties.lookup_value ("Volume", VariantType.DOUBLE);
216+ if (volume_variant != null) {
217+ var volume = volume_variant.get_double ();
218+ if (volume >= 0 && _volume != volume) {
219+ _volume = volume;
220+ this.notify_property("volume");
221+ }
222+ }
223+
224+ Variant mute_variant = changed_properties.lookup_value ("Muted", VariantType.BOOLEAN);
225+ if (mute_variant != null) {
226+ _mute = mute_variant.get_boolean ();
227+ this.notify_property("mute");
228+ }
229+
230+ Variant last_running_player_variant = changed_properties.lookup_value ("LastRunningPlayer", VariantType.STRING);
231+ if (last_running_player_variant != null) {
232+ _last_running_player = last_running_player_variant.get_string ();
233+ this.notify_property("last-running-player");
234+ }
235+ }
236+
237+ private async void setup_user_proxy (string? username_in = null)
238+ {
239+ var username = username_in;
240+ _user_proxy = null;
241+
242+ // Look up currently selected greeter user, if asked
243+ if (username == null) {
244+ try {
245+ username = yield _greeter_proxy.get_active_entry ();
246+ if (username == "" || username == null)
247+ return;
248+ } catch (GLib.Error e) {
249+ warning ("unable to find Accounts path for user %s: %s", username == null ? "null" : username, e.message);
250+ return;
251+ }
252+ }
253+
254+ // Get master AccountsService object
255+ DBusProxy accounts_proxy;
256+ try {
257+ accounts_proxy = yield DBusProxy.create_for_bus (BusType.SYSTEM, DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, null, "org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts");
258+ } catch (GLib.Error e) {
259+ warning ("unable to get greeter proxy: %s", e.message);
260+ return;
261+ }
262+
263+ // Find user's AccountsService object
264+ try {
265+ var user_path_variant = yield accounts_proxy.call ("FindUserByName", new Variant ("(s)", username), DBusCallFlags.NONE, -1);
266+ string user_path;
267+ if (user_path_variant.check_format_string ("(o)", true)) {
268+ user_path_variant.get ("(o)", out user_path);
269+ _user_proxy = yield DBusProxy.create_for_bus (BusType.SYSTEM, DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, "org.freedesktop.Accounts", user_path, "com.ubuntu.AccountsService.Sound");
270+ } else {
271+ warning ("Unable to find user name after calling FindUserByName. Expected type: %s and obtained %s", "(o)", user_path_variant.get_type_string () );
272+ return;
273+ }
274+ } catch (GLib.Error e) {
275+ warning ("unable to find Accounts path for user %s: %s", username, e.message);
276+ return;
277+ }
278+
279+ // Get current values and listen for changes
280+ _user_proxy.g_properties_changed.connect (accountsservice_props_changed_cb);
281+ try {
282+ var props_variant = yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "GetAll", new Variant ("(s)", _user_proxy.get_interface_name ()), null, DBusCallFlags.NONE, -1);
283+ if (props_variant.check_format_string ("(@a{sv})", true)) {
284+ Variant props;
285+ props_variant.get ("(@a{sv})", out props);
286+ accountsservice_props_changed_cb(_user_proxy, props, null);
287+ } else {
288+ warning ("Unable to get accounts service properties after calling GetAll. Expected type: %s and obtained %s", "(@a{sv})", props_variant.get_type_string () );
289+ return;
290+ }
291+ } catch (GLib.Error e) {
292+ debug("Unable to get properties for user %s at first try: %s", username, e.message);
293+ }
294+ }
295+
296+ private void greeter_user_changed (string username)
297+ {
298+ setup_user_proxy.begin (username);
299+ }
300+
301+ private async void setup_accountsservice ()
302+ {
303+ if (Environment.get_variable ("XDG_SESSION_CLASS") == "greeter") {
304+ try {
305+ _greeter_proxy = yield Bus.get_proxy (BusType.SESSION, "com.canonical.UnityGreeter", "/list");
306+ } catch (GLib.Error e) {
307+ warning ("unable to get greeter proxy: %s", e.message);
308+ return;
309+ }
310+ _greeter_proxy.entry_selected.connect (greeter_user_changed);
311+ yield setup_user_proxy ();
312+ } else {
313+ // We are in a user session. We just need our own proxy
314+ unowned string username = Environment.get_variable ("USER");
315+ if (username != null && username != "") {
316+ yield setup_user_proxy (username);
317+ }
318+ }
319+ }
320+
321+ private async void sync_last_running_player_to_accountsservice (string last_running_player)
322+ {
323+ if (_user_proxy == null)
324+ return;
325+
326+ try {
327+ yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "Set", new Variant ("(ssv)", _user_proxy.get_interface_name (), "LastRunningPlayer", new Variant ("s", last_running_player)), null, DBusCallFlags.NONE, -1, _dbus_call_cancellable);
328+ } catch (GLib.Error e) {
329+ warning ("unable to sync last running player %s to AccountsService: %s",last_running_player, e.message);
330+ }
331+ _last_running_player = last_running_player;
332+ }
333+
334+ private async void sync_volume_to_accountsservice (double volume)
335+ {
336+ if (_user_proxy == null)
337+ return;
338+
339+ try {
340+ yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "Set", new Variant ("(ssv)", _user_proxy.get_interface_name (), "Volume", new Variant ("d", volume)), null, DBusCallFlags.NONE, -1, _dbus_call_cancellable);
341+ } catch (GLib.Error e) {
342+ warning ("unable to sync volume %f to AccountsService: %s", volume, e.message);
343+ }
344+ }
345+
346+ private async void sync_mute_to_accountsservice (bool mute)
347+ {
348+ if (_user_proxy == null)
349+ return;
350+
351+ try {
352+ yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "Set", new Variant ("(ssv)", _user_proxy.get_interface_name (), "Muted", new Variant ("b", mute)), null, DBusCallFlags.NONE, -1, _dbus_call_cancellable);
353+ } catch (GLib.Error e) {
354+ warning ("unable to sync mute %s to AccountsService: %s", mute ? "true" : "false", e.message);
355+ }
356+ }
357+}
358+
359+
360
361=== modified file 'src/main.c'
362--- src/main.c 2015-12-29 17:03:41 +0000
363+++ src/main.c 2016-03-03 08:59:58 +0000
364@@ -27,9 +27,9 @@
365 static gboolean
366 sigterm_handler (gpointer data)
367 {
368- g_debug("Got SIGTERM");
369- g_main_loop_quit((GMainLoop *)data);
370- return G_SOURCE_REMOVE;
371+ g_debug("Got SIGTERM");
372+ g_main_loop_quit((GMainLoop *)data);
373+ return G_SOURCE_REMOVE;
374 }
375
376 static void
377@@ -37,8 +37,8 @@
378 const gchar * name,
379 gpointer user_data)
380 {
381- g_warning("Name lost or unable to acquire bus: %s", name);
382- g_main_loop_quit((GMainLoop *)user_data);
383+ g_warning("Name lost or unable to acquire bus: %s", name);
384+ g_main_loop_quit((GMainLoop *)user_data);
385 }
386
387 static void
388@@ -46,66 +46,68 @@
389 const gchar *name,
390 gpointer user_data)
391 {
392- MediaPlayerList * playerlist = NULL;
393- IndicatorSoundOptions * options = NULL;
394- VolumeControlPulse * volume = NULL;
395- AccountsServiceUser * accounts = NULL;
396- VolumeWarning * warning = NULL;
397-
398-
399- if (g_strcmp0("lightdm", g_get_user_name()) == 0) {
400- playerlist = MEDIA_PLAYER_LIST(media_player_list_greeter_new());
401- } else {
402- playerlist = MEDIA_PLAYER_LIST(media_player_list_mpris_new());
403- accounts = accounts_service_user_new();
404- }
405-
406- pgloop = pa_glib_mainloop_new(NULL);
407- options = indicator_sound_options_gsettings_new();
408- volume = volume_control_pulse_new(options, pgloop);
409- warning = volume_warning_pulse_new(options, pgloop);
410-
411- service = indicator_sound_service_new (playerlist, volume, accounts, options, warning);
412-
413- g_clear_object(&playerlist);
414- g_clear_object(&options);
415- g_clear_object(&volume);
416- g_clear_object(&accounts);
417- g_clear_object(&warning);
418+ MediaPlayerList * playerlist = NULL;
419+ IndicatorSoundOptions * options = NULL;
420+ VolumeControlPulse * volume = NULL;
421+ AccountsServiceUser * accounts = NULL;
422+ VolumeWarning * warning = NULL;
423+ AccountsServiceAccess * accounts_service_access = NULL;
424+
425+
426+ if (g_strcmp0("lightdm", g_get_user_name()) == 0) {
427+ playerlist = MEDIA_PLAYER_LIST(media_player_list_greeter_new());
428+ } else {
429+ playerlist = MEDIA_PLAYER_LIST(media_player_list_mpris_new());
430+ accounts = accounts_service_user_new();
431+ }
432+
433+ pgloop = pa_glib_mainloop_new(NULL);
434+ options = indicator_sound_options_gsettings_new();
435+ accounts_service_access = accounts_service_access_new();
436+ volume = volume_control_pulse_new(options, pgloop, accounts_service_access);
437+ warning = volume_warning_pulse_new(options, pgloop);
438+
439+ service = indicator_sound_service_new (playerlist, volume, accounts, options, warning, accounts_service_access);
440+
441+ g_clear_object(&playerlist);
442+ g_clear_object(&options);
443+ g_clear_object(&volume);
444+ g_clear_object(&accounts);
445+ g_clear_object(&warning);
446 }
447
448 int
449 main (int argc, char ** argv)
450 {
451- GMainLoop * loop = NULL;
452- bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
453- setlocale (LC_ALL, "");
454- bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
455-
456- /* Build Mainloop */
457- loop = g_main_loop_new(NULL, FALSE);
458-
459- g_unix_signal_add(SIGTERM, sigterm_handler, loop);
460-
461- /* Initialize libnotify */
462- notify_init ("indicator-sound");
463-
464- g_bus_own_name(G_BUS_TYPE_SESSION,
465- "com.canonical.indicator.sound",
466- G_BUS_NAME_OWNER_FLAGS_NONE,
467- on_bus_acquired,
468- NULL, /* name acquired */
469- on_name_lost,
470- loop,
471- NULL);
472-
473- g_main_loop_run(loop);
474-
475- g_clear_object(&service);
476- g_clear_pointer(&pgloop, pa_glib_mainloop_free);
477-
478- notify_uninit();
479-
480- return 0;
481+ GMainLoop * loop = NULL;
482+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
483+ setlocale (LC_ALL, "");
484+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
485+
486+ /* Build Mainloop */
487+ loop = g_main_loop_new(NULL, FALSE);
488+
489+ g_unix_signal_add(SIGTERM, sigterm_handler, loop);
490+
491+ /* Initialize libnotify */
492+ notify_init ("indicator-sound");
493+
494+ g_bus_own_name(G_BUS_TYPE_SESSION,
495+ "com.canonical.indicator.sound",
496+ G_BUS_NAME_OWNER_FLAGS_NONE,
497+ on_bus_acquired,
498+ NULL, /* name acquired */
499+ on_name_lost,
500+ loop,
501+ NULL);
502+
503+ g_main_loop_run(loop);
504+
505+ g_clear_object(&service);
506+ g_clear_pointer(&pgloop, pa_glib_mainloop_free);
507+
508+ notify_uninit();
509+
510+ return 0;
511 }
512
513
514=== modified file 'src/service.vala'
515--- src/service.vala 2016-02-10 13:08:49 +0000
516+++ src/service.vala 2016-03-03 08:59:58 +0000
517@@ -20,7 +20,9 @@
518 public class IndicatorSound.Service: Object {
519 DBusConnection bus;
520
521- public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts, Options options, VolumeWarning volume_warning) {
522+ public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts, Options options, VolumeWarning volume_warning, AccountsServiceAccess? accounts_service_access) {
523+
524+ _accounts_service_access = accounts_service_access;
525
526 try {
527 bus = Bus.get_sync(GLib.BusType.SESSION);
528@@ -60,7 +62,6 @@
529 headphones = false;
530 break;
531 }
532- message("setting _volume_warning.headphones_active to %d", (int)headphones);
533 _volume_warning.headphones_active = headphones;
534
535 update_root_icon();
536@@ -91,12 +92,11 @@
537 this.actions.add_action (this.create_high_volume_action ());
538 this.actions.add_action (this.create_volume_sync_action ());
539
540- string last_player = this.settings.get_string ("last-running-player");
541 this.menus = new HashTable<string, SoundMenu> (str_hash, str_equal);
542- this.menus.insert ("desktop_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_MUTE | SoundMenu.DisplayFlags.HIDE_PLAYERS | SoundMenu.DisplayFlags.GREETER_PLAYERS, last_player));
543- this.menus.insert ("phone_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_SILENT_MODE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS | SoundMenu.DisplayFlags.GREETER_PLAYERS, last_player));
544- this.menus.insert ("desktop", new SoundMenu ("indicator.desktop-settings", SoundMenu.DisplayFlags.SHOW_MUTE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS_PLAY_CONTROLS | SoundMenu.DisplayFlags.ADD_PLAY_CONTROL_INACTIVE_PLAYER, last_player));
545- this.menus.insert ("phone", new SoundMenu ("indicator.phone-settings", SoundMenu.DisplayFlags.SHOW_SILENT_MODE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS, last_player));
546+ this.menus.insert ("desktop_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_MUTE | SoundMenu.DisplayFlags.HIDE_PLAYERS | SoundMenu.DisplayFlags.GREETER_PLAYERS));
547+ this.menus.insert ("phone_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_SILENT_MODE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS | SoundMenu.DisplayFlags.GREETER_PLAYERS));
548+ this.menus.insert ("desktop", new SoundMenu ("indicator.desktop-settings", SoundMenu.DisplayFlags.SHOW_MUTE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS_PLAY_CONTROLS | SoundMenu.DisplayFlags.ADD_PLAY_CONTROL_INACTIVE_PLAYER));
549+ this.menus.insert ("phone", new SoundMenu ("indicator.phone-settings", SoundMenu.DisplayFlags.SHOW_SILENT_MODE | SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS));
550
551 this.menus.@foreach ( (profile, menu) => {
552 this.volume_control.bind_property ("active-mic", menu, "show-mic-volume", BindingFlags.SYNC_CREATE);
553@@ -112,7 +112,13 @@
554
555 this.menus.@foreach ( (profile, menu) => {
556 menu.last_player_updated.connect ((player_id) => {
557- this.settings.set_value ("last-running-player", player_id);
558+ this._accounts_service_access.last_running_player = player_id;
559+ });
560+ });
561+
562+ this._accounts_service_access.notify["last-running-player"].connect(() => {
563+ this.menus.@foreach ( (profile, menu) => {
564+ menu.set_default_player (this._accounts_service_access.last_running_player);
565 });
566 });
567
568@@ -199,6 +205,7 @@
569 private Options _options;
570 private VolumeWarning _volume_warning;
571 private IndicatorSound.InfoNotification _info_notification = new IndicatorSound.InfoNotification();
572+ private AccountsServiceAccess _accounts_service_access;
573
574 const double volume_step_percentage = 0.06;
575
576
577=== modified file 'src/sound-menu.vala'
578--- src/sound-menu.vala 2016-02-10 13:08:49 +0000
579+++ src/sound-menu.vala 2016-03-03 08:59:58 +0000
580@@ -38,7 +38,7 @@
581
582 const string PLAYBACK_ITEM_TYPE = "com.canonical.unity.playback-item";
583
584- public SoundMenu (string? settings_action, DisplayFlags flags, string default_player_id) {
585+ public SoundMenu (string? settings_action, DisplayFlags flags) {
586 /* A sound menu always has at least two sections: the volume section (this.volume_section)
587 * at the start of the menu, and the settings section at the end. Between those two,
588 * it has a dynamic amount of player sections, one for each registered player.
589@@ -83,9 +83,6 @@
590 this.notify_handlers = new HashTable<MediaPlayer, ulong> (direct_hash, direct_equal);
591
592 this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0;
593-
594- this.default_player = default_player_id;
595-
596 }
597
598 ~SoundMenu () {
599@@ -95,6 +92,16 @@
600 }
601 }
602
603+ public void set_default_player (string default_player_id) {
604+ this.default_player = default_player_id;
605+ foreach (var player_stored in notify_handlers.get_keys ()) {
606+ int index = this.find_player_section(player_stored);
607+ if (index != -1 && player_stored.id == this.default_player) {
608+ add_player_playback_controls (player_stored, index, true);
609+ }
610+ }
611+ }
612+
613 DBusConnection? bus = null;
614 uint export_id = 0;
615
616@@ -171,7 +178,17 @@
617 }
618 }
619 }
620-
621+
622+ private void check_last_running_player () {
623+ foreach (var player in notify_handlers.get_keys ()) {
624+ if (player.is_running && number_of_running_players == 1) {
625+ // this is the first or the last player running...
626+ // store its id
627+ this.last_player_updated (player.id);
628+ }
629+ }
630+ }
631+
632 public void add_player (MediaPlayer player) {
633 if (this.notify_handlers.contains (player))
634 return;
635@@ -198,11 +215,15 @@
636 // we need to update the rest of players, because we might have
637 // a non running player still showing the playback controls
638 update_all_players_play_section();
639+
640+ check_last_running_player ();
641 });
642 this.notify_handlers.insert (player, handler_id);
643
644 player.playlists_changed.connect (this.update_playlists);
645 player.playbackstatus_changed.connect (this.update_playbackstatus);
646+
647+ check_last_running_player ();
648 }
649
650 public void remove_player (MediaPlayer player) {
651@@ -217,6 +238,8 @@
652
653 /* this'll drop our ref to it */
654 this.notify_handlers.remove (player);
655+
656+ check_last_running_player ();
657 }
658
659 public void update_volume_slider (VolumeControl.ActiveOutput active_output) {
660@@ -368,16 +391,11 @@
661 this.menu.remove (index);
662 }
663
664- void update_player_section (MediaPlayer player, int index) {
665+ void add_player_playback_controls (MediaPlayer player, int index, bool adding_default_player) {
666 var player_section = this.menu.get_item_link(index, Menu.LINK_SECTION) as Menu;
667
668 int play_control_index = find_player_playback_controls_section (player_section);
669- if (player.is_running && number_of_running_players == 1) {
670- // this is the first or the last player running...
671- // store its id
672- this.last_player_updated (player.id);
673- }
674- if (player.is_running || !this.hide_inactive_player_controls) {
675+ if (player.is_running || !this.hide_inactive_player_controls || (number_of_running_players == 0 && adding_default_player) ) {
676 MenuItem playback_item = create_playback_menu_item (player);
677 if (play_control_index != -1) {
678 player_section.remove (PlayerSectionPosition.PLAYER_CONTROLS);
679@@ -389,7 +407,11 @@
680 player_section.remove (PlayerSectionPosition.PLAYLIST);
681 player_section.remove (PlayerSectionPosition.PLAYER_CONTROLS);
682 }
683- }
684+ }
685+ }
686+
687+ void update_player_section (MediaPlayer player, int index) {
688+ add_player_playback_controls (player, index, false);
689 }
690
691 void update_playlists (MediaPlayer player) {
692
693=== modified file 'src/volume-control-pulse.vala'
694--- src/volume-control-pulse.vala 2016-01-07 05:28:51 +0000
695+++ src/volume-control-pulse.vala 2016-03-03 08:59:58 +0000
696@@ -22,13 +22,6 @@
697 using Notify;
698 using Gee;
699
700-[DBus (name="com.canonical.UnityGreeter.List")]
701-interface GreeterListInterface : Object
702-{
703- public abstract async string get_active_entry () throws IOError;
704- public signal void entry_selected (string entry_name);
705-}
706-
707 public class VolumeControlPulse : VolumeControl
708 {
709 private unowned PulseAudio.GLibMainLoop loop = null;
710@@ -55,20 +48,17 @@
711 private string? _objp_role_phone = null;
712 private uint _pa_volume_sig_count = 0;
713
714- private DBusProxy _user_proxy;
715- private GreeterListInterface _greeter_proxy;
716- private Cancellable _mute_cancellable;
717- private Cancellable _volume_cancellable;
718 private uint _local_volume_timer = 0;
719 private uint _accountservice_volume_timer = 0;
720 private bool _send_next_local_volume = false;
721 private double _account_service_volume = 0.0;
722 private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS;
723+ private AccountsServiceAccess _accounts_service_access;
724
725 /** true when a microphone is active **/
726 public override bool active_mic { get; private set; default = false; }
727
728- public VolumeControlPulse (IndicatorSound.Options options, PulseAudio.GLibMainLoop loop)
729+ public VolumeControlPulse (IndicatorSound.Options options, PulseAudio.GLibMainLoop loop, AccountsServiceAccess? accounts_service_access)
730 {
731 base(options);
732
733@@ -77,11 +67,14 @@
734
735 this.loop = loop;
736
737- _mute_cancellable = new Cancellable ();
738- _volume_cancellable = new Cancellable ();
739-
740- setup_accountsservice.begin ();
741-
742+ _accounts_service_access = accounts_service_access;
743+ this._accounts_service_access.notify["volume"].connect(() => {
744+ if (this._accounts_service_access.volume >= 0 && _account_service_volume != this._accounts_service_access.volume) {
745+ _account_service_volume = this._accounts_service_access.volume;
746+ // we need to wait for this to settle.
747+ start_account_service_volume_timer();
748+ }
749+ });
750 this.reconnect_to_pulse ();
751 }
752
753@@ -537,7 +530,7 @@
754 public override void set_mute (bool mute)
755 {
756 if (set_mute_internal (mute))
757- sync_mute_to_accountsservice.begin (mute);
758+ _accounts_service_access.mute = mute;
759 }
760
761 public void toggle_mute ()
762@@ -773,128 +766,6 @@
763 }
764
765 /* AccountsService operations */
766- private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties)
767- {
768- Variant volume_variant = changed_properties.lookup_value ("Volume", VariantType.DOUBLE);
769- if (volume_variant != null) {
770- var volume = volume_variant.get_double ();
771- if (volume >= 0) {
772- _account_service_volume = volume;
773- // we need to wait for this to settle.
774- start_account_service_volume_timer();
775- }
776- }
777-
778- Variant mute_variant = changed_properties.lookup_value ("Muted", VariantType.BOOLEAN);
779- if (mute_variant != null) {
780- var mute = mute_variant.get_boolean ();
781- set_mute_internal (mute);
782- }
783- }
784-
785- private async void setup_user_proxy (string? username_in = null)
786- {
787- var username = username_in;
788- _user_proxy = null;
789-
790- // Look up currently selected greeter user, if asked
791- if (username == null) {
792- try {
793- username = yield _greeter_proxy.get_active_entry ();
794- if (username == "" || username == null)
795- return;
796- } catch (GLib.Error e) {
797- warning ("unable to find Accounts path for user %s: %s", username, e.message);
798- return;
799- }
800- }
801-
802- // Get master AccountsService object
803- DBusProxy accounts_proxy;
804- try {
805- accounts_proxy = yield DBusProxy.create_for_bus (BusType.SYSTEM, DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, null, "org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts");
806- } catch (GLib.Error e) {
807- warning ("unable to get greeter proxy: %s", e.message);
808- return;
809- }
810-
811- // Find user's AccountsService object
812- try {
813- var user_path_variant = yield accounts_proxy.call ("FindUserByName", new Variant ("(s)", username), DBusCallFlags.NONE, -1);
814- string user_path;
815- user_path_variant.get ("(o)", out user_path);
816- _user_proxy = yield DBusProxy.create_for_bus (BusType.SYSTEM, DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, "org.freedesktop.Accounts", user_path, "com.ubuntu.AccountsService.Sound");
817- } catch (GLib.Error e) {
818- warning ("unable to find Accounts path for user %s: %s", username, e.message);
819- return;
820- }
821-
822- // Get current values and listen for changes
823- _user_proxy.g_properties_changed.connect (accountsservice_props_changed_cb);
824- try {
825- var props_variant = yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "GetAll", new Variant ("(s)", _user_proxy.get_interface_name ()), null, DBusCallFlags.NONE, -1);
826- Variant props;
827- props_variant.get ("(@a{sv})", out props);
828- accountsservice_props_changed_cb(_user_proxy, props, null);
829- } catch (GLib.Error e) {
830- debug("Unable to get properties for user %s at first try: %s", username, e.message);
831- }
832- }
833-
834- private void greeter_user_changed (string username)
835- {
836- setup_user_proxy.begin (username);
837- }
838-
839- private async void setup_accountsservice ()
840- {
841- if (Environment.get_variable ("XDG_SESSION_CLASS") == "greeter") {
842- try {
843- _greeter_proxy = yield Bus.get_proxy (BusType.SESSION, "com.canonical.UnityGreeter", "/list");
844- } catch (GLib.Error e) {
845- warning ("unable to get greeter proxy: %s", e.message);
846- return;
847- }
848- _greeter_proxy.entry_selected.connect (greeter_user_changed);
849- yield setup_user_proxy ();
850- } else {
851- // We are in a user session. We just need our own proxy
852- unowned string username = Environment.get_variable ("USER");
853- if (username != "" && username != null) {
854- yield setup_user_proxy (username);
855- }
856- }
857- }
858-
859- private async void sync_mute_to_accountsservice (bool mute)
860- {
861- if (_user_proxy == null)
862- return;
863-
864- _mute_cancellable.cancel ();
865- _mute_cancellable.reset ();
866-
867- try {
868- yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "Set", new Variant ("(ssv)", _user_proxy.get_interface_name (), "Muted", new Variant ("b", mute)), null, DBusCallFlags.NONE, -1, _mute_cancellable);
869- } catch (GLib.Error e) {
870- warning ("unable to sync mute to AccountsService: %s", e.message);
871- }
872- }
873-
874- private async void sync_volume_to_accountsservice (VolumeControl.Volume volume)
875- {
876- if (_user_proxy == null)
877- return;
878-
879- _volume_cancellable.cancel ();
880- _volume_cancellable.reset ();
881-
882- try {
883- yield _user_proxy.get_connection ().call (_user_proxy.get_name (), _user_proxy.get_object_path (), "org.freedesktop.DBus.Properties", "Set", new Variant ("(ssv)", _user_proxy.get_interface_name (), "Volume", new Variant ("d", volume.volume)), null, DBusCallFlags.NONE, -1, _volume_cancellable);
884- } catch (GLib.Error e) {
885- warning ("unable to sync volume to AccountsService: %s", e.message);
886- }
887- }
888
889 private void start_local_volume_timer()
890 {
891@@ -904,7 +775,7 @@
892 stop_account_service_volume_timer();
893
894 if (_local_volume_timer == 0) {
895- sync_volume_to_accountsservice.begin (_volume);
896+ _accounts_service_access.volume = _volume.volume;
897 _local_volume_timer = Timeout.add_seconds (1, local_volume_changed_timeout);
898 } else {
899 _send_next_local_volume = true;
900
901=== modified file 'tests/integration/indicator-sound-test-base.cpp'
902--- tests/integration/indicator-sound-test-base.cpp 2016-01-29 11:16:34 +0000
903+++ tests/integration/indicator-sound-test-base.cpp 2016-03-03 08:59:58 +0000
904@@ -448,13 +448,37 @@
905 return true;
906 }
907
908+QVariant IndicatorSoundTestBase::waitPropertyChanged(QSignalSpy *signalSpy, QString property)
909+{
910+ QVariant ret_val;
911+ if (signalSpy)
912+ {
913+ signalSpy->wait();
914+ if (signalSpy->count())
915+ {
916+ QList<QVariant> arguments = signalSpy->takeLast();
917+ if (arguments.size() == 3 && static_cast<QMetaType::Type>(arguments.at(1).type()) == QMetaType::QVariantMap)
918+ {
919+ QMap<QString, QVariant> map = arguments.at(1).toMap();
920+ QMap<QString, QVariant>::iterator iter = map.find(property);
921+ if (iter != map.end())
922+ {
923+ return iter.value();
924+ }
925+ }
926+ }
927+ }
928+ return ret_val;
929+}
930 bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
931 {
932- if (signal_spy_volume_changed_)
933- {
934- return signal_spy_volume_changed_->wait();
935- }
936- return false;
937+ QVariant val = waitPropertyChanged(signal_spy_volume_changed_.get(), "Volume");
938+ return !val.isNull();
939+}
940+
941+QVariant IndicatorSoundTestBase::waitLastRunningPlayerChanged()
942+{
943+ return waitPropertyChanged(signal_spy_volume_changed_.get(), "LastRunningPlayer");
944 }
945
946 void IndicatorSoundTestBase::initializeAccountsInterface()
947
948=== modified file 'tests/integration/indicator-sound-test-base.h'
949--- tests/integration/indicator-sound-test-base.h 2016-01-29 11:16:34 +0000
950+++ tests/integration/indicator-sound-test-base.h 2016-03-03 08:59:58 +0000
951@@ -144,6 +144,10 @@
952
953 float getVolumeValue(bool *isValid = nullptr);
954
955+ static QVariant waitPropertyChanged(QSignalSpy * signalSpy, QString property);
956+
957+ QVariant waitLastRunningPlayerChanged();
958+
959 QtDBusTest::DBusTestRunner dbusTestRunner;
960
961 QtDBusMock::DBusMock dbusMock;
962
963=== modified file 'tests/integration/test-indicator.cpp'
964--- tests/integration/test-indicator.cpp 2016-02-10 13:08:49 +0000
965+++ tests/integration/test-indicator.cpp 2016-03-03 08:59:58 +0000
966@@ -394,9 +394,7 @@
967 .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
968 )
969 .item(mh::MenuItemMatcher()
970- .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
971 .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
972- .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
973 .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
974 )
975 )
976@@ -925,16 +923,75 @@
977 .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
978 )
979 .item(mh::MenuItemMatcher()
980- .string_attribute("x-canonical-previous-action","indicator.previous.testplayer3.desktop")
981- .string_attribute("x-canonical-play-action","indicator.play.testplayer3.desktop")
982- .string_attribute("x-canonical-next-action","indicator.next.testplayer3.desktop")
983- .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
984- )
985- )
986- .item(mh::MenuItemMatcher()
987- .label("Sound Settings…")
988- )
989- ).match());
990+ .string_attribute("x-canonical-play-action","indicator.play.testplayer3.desktop")
991+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
992+ )
993+ )
994+ .item(mh::MenuItemMatcher()
995+ .label("Sound Settings…")
996+ )
997+ ).match());
998+
999+ // check that the last running player is the one we expect
1000+ QVariant lastPlayerRunning = waitLastRunningPlayerChanged();
1001+ EXPECT_TRUE(lastPlayerRunning.type() == QVariant::String);
1002+ EXPECT_EQ(lastPlayerRunning.toString(), "testplayer3.desktop");
1003+
1004+ // restart the indicator to simulate a new user session
1005+ ASSERT_NO_THROW(startIndicator());
1006+
1007+ // check that player 3 is the only one with playback controls
1008+ // as it was the last one being stopped
1009+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
1010+ .item(mh::MenuItemMatcher()
1011+ .action("indicator.root")
1012+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
1013+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
1014+ .mode(mh::MenuItemMatcher::Mode::all)
1015+ .submenu()
1016+ .item(mh::MenuItemMatcher()
1017+ .section()
1018+ .item(mh::MenuItemMatcher().checkbox()
1019+ .label("Mute")
1020+ )
1021+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
1022+ )
1023+ .item(mh::MenuItemMatcher()
1024+ .section()
1025+ .item(mh::MenuItemMatcher()
1026+ .action("indicator.testplayer3.desktop")
1027+ .label("TestPlayer3")
1028+ .themed_icon("icon", {"testplayer"})
1029+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
1030+ )
1031+ .item(mh::MenuItemMatcher()
1032+ .string_attribute("x-canonical-play-action","indicator.play.testplayer3.desktop")
1033+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
1034+ )
1035+ )
1036+ .item(mh::MenuItemMatcher()
1037+ .section()
1038+ .item(mh::MenuItemMatcher()
1039+ .action("indicator.testplayer1.desktop")
1040+ .label("TestPlayer1")
1041+ .themed_icon("icon", {"testplayer"})
1042+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
1043+ )
1044+ )
1045+ .item(mh::MenuItemMatcher()
1046+ .section()
1047+ .item(mh::MenuItemMatcher()
1048+ .action("indicator.testplayer2.desktop")
1049+ .label("TestPlayer2")
1050+ .themed_icon("icon", {"testplayer"})
1051+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
1052+ )
1053+ )
1054+ .item(mh::MenuItemMatcher()
1055+ .label("Sound Settings…")
1056+ )
1057+ ).match());
1058+
1059 }
1060
1061 TEST_F(TestIndicator, DesktopMprisPlayerButtonsState)
1062
1063=== modified file 'tests/notifications-test.cc'
1064--- tests/notifications-test.cc 2016-01-05 19:15:46 +0000
1065+++ tests/notifications-test.cc 2016-03-03 08:59:58 +0000
1066@@ -36,592 +36,602 @@
1067
1068 class NotificationsTest : public ::testing::Test
1069 {
1070- protected:
1071- DbusTestService * service = NULL;
1072-
1073- GDBusConnection * session = NULL;
1074- std::shared_ptr<NotificationsMock> notifications;
1075-
1076- virtual void SetUp() {
1077- g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
1078- g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
1079-
1080- service = dbus_test_service_new(NULL);
1081- dbus_test_service_set_bus(service, DBUS_TEST_SERVICE_BUS_SESSION);
1082-
1083- /* Useful for debugging test failures, not needed all the time (until it fails) */
1084- #if 0
1085- auto bustle = std::shared_ptr<DbusTestTask>([]() {
1086- DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new("notifications-test.bustle"));
1087- dbus_test_task_set_name(bustle, "Bustle");
1088- dbus_test_task_set_bus(bustle, DBUS_TEST_SERVICE_BUS_SESSION);
1089- return bustle;
1090- }(), [](DbusTestTask * bustle) {
1091- g_clear_object(&bustle);
1092- });
1093- dbus_test_service_add_task(service, bustle.get());
1094- #endif
1095-
1096- notifications = std::make_shared<NotificationsMock>();
1097-
1098- dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1099- dbus_test_service_start_tasks(service);
1100-
1101- session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1102- ASSERT_NE(nullptr, session);
1103- g_dbus_connection_set_exit_on_close(session, FALSE);
1104- g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session);
1105-
1106- /* This is done in main.c */
1107- notify_init("indicator-sound");
1108- }
1109-
1110- virtual void TearDown() {
1111- if (notify_is_initted())
1112- notify_uninit();
1113-
1114- notifications.reset();
1115- g_clear_object(&service);
1116-
1117- g_object_unref(session);
1118-
1119- unsigned int cleartry = 0;
1120- while (session != NULL && cleartry < 100) {
1121- loop(100);
1122- cleartry++;
1123- }
1124-
1125- ASSERT_EQ(nullptr, session);
1126- }
1127-
1128- static gboolean timeout_cb (gpointer user_data) {
1129- GMainLoop * loop = static_cast<GMainLoop *>(user_data);
1130- g_main_loop_quit(loop);
1131- return G_SOURCE_REMOVE;
1132- }
1133-
1134- void loop (unsigned int ms) {
1135- GMainLoop * loop = g_main_loop_new(NULL, FALSE);
1136- g_timeout_add(ms, timeout_cb, loop);
1137- g_main_loop_run(loop);
1138- g_main_loop_unref(loop);
1139- }
1140-
1141- void loop_until(const std::function<bool()>& test, unsigned int max_ms=50, unsigned int test_interval_ms=10) {
1142-
1143- // g_timeout's callback only allows a single pointer,
1144- // so use a temporary stack struct to wedge everything into one pointer
1145- struct CallbackData {
1146- const std::function<bool()>& test;
1147- const gint64 deadline;
1148- GMainLoop* loop = g_main_loop_new(nullptr, false);
1149- CallbackData (const std::function<bool()>& f, unsigned int max_ms):
1150- test{f},
1151- deadline{g_get_monotonic_time() + (max_ms*1000)} {}
1152- ~CallbackData() {g_main_loop_unref(loop);}
1153- } data(test, max_ms);
1154-
1155- // tell the timer to stop looping on success or deadline
1156- auto timerfunc = [](gpointer gdata) -> gboolean {
1157- auto& data = *static_cast<CallbackData*>(gdata);
1158- if (!data.test() && (g_get_monotonic_time() < data.deadline))
1159- return G_SOURCE_CONTINUE;
1160- g_main_loop_quit(data.loop);
1161- return G_SOURCE_REMOVE;
1162- };
1163-
1164- // start looping
1165- g_timeout_add (std::min(max_ms, test_interval_ms), timerfunc, &data);
1166- g_main_loop_run(data.loop);
1167- }
1168-
1169- void loop_until_notifications(unsigned int max_seconds=1) {
1170- auto test = [this]{ return !notifications->getNotifications().empty(); };
1171- loop_until(test, max_seconds);
1172- }
1173-
1174- static int unref_idle (gpointer user_data) {
1175- g_variant_unref(static_cast<GVariant *>(user_data));
1176- return G_SOURCE_REMOVE;
1177- }
1178-
1179- std::shared_ptr<MediaPlayerList> playerListMock () {
1180- auto playerList = std::shared_ptr<MediaPlayerList>(
1181- MEDIA_PLAYER_LIST(media_player_list_mock_new()),
1182- [](MediaPlayerList * list) {
1183- g_clear_object(&list);
1184- });
1185- return playerList;
1186- }
1187-
1188- std::shared_ptr<IndicatorSoundOptions> optionsMock () {
1189- auto options = std::shared_ptr<IndicatorSoundOptions>(
1190- INDICATOR_SOUND_OPTIONS(options_mock_new()),
1191- [](IndicatorSoundOptions * options){
1192- g_clear_object(&options);
1193- });
1194- return options;
1195- }
1196-
1197- std::shared_ptr<VolumeControl> volumeControlMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) {
1198- auto volumeControl = std::shared_ptr<VolumeControl>(
1199- VOLUME_CONTROL(volume_control_mock_new(optionsMock.get())),
1200- [](VolumeControl * control){
1201- g_clear_object(&control);
1202- });
1203- return volumeControl;
1204- }
1205-
1206- std::shared_ptr<VolumeWarning> volumeWarningMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) {
1207- auto volumeWarning = std::shared_ptr<VolumeWarning>(
1208- VOLUME_WARNING(volume_warning_mock_new(optionsMock.get())),
1209- [](VolumeWarning * warning){
1210- g_clear_object(&warning);
1211- });
1212- return volumeWarning;
1213- }
1214-
1215- std::shared_ptr<IndicatorSoundService> standardService (
1216- const std::shared_ptr<VolumeControl>& volumeControl,
1217- const std::shared_ptr<MediaPlayerList>& playerList,
1218- const std::shared_ptr<IndicatorSoundOptions>& options,
1219- const std::shared_ptr<VolumeWarning>& warning) {
1220- auto soundService = std::shared_ptr<IndicatorSoundService>(
1221- indicator_sound_service_new(playerList.get(), volumeControl.get(), nullptr, options.get(), warning.get()),
1222- [](IndicatorSoundService * service){
1223- g_clear_object(&service);
1224- });
1225-
1226- return soundService;
1227- }
1228-
1229- void setMockVolume (std::shared_ptr<VolumeControl> volumeControl, double volume, VolumeControlVolumeReasons reason = VOLUME_CONTROL_VOLUME_REASONS_USER_KEYPRESS) {
1230- VolumeControlVolume * vol = volume_control_volume_new();
1231- vol->volume = volume;
1232- vol->reason = reason;
1233-
1234- volume_control_set_volume(volumeControl.get(), vol);
1235- g_object_unref(vol);
1236- }
1237-
1238- void setIndicatorShown (bool shown) {
1239- auto bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
1240-
1241- g_dbus_connection_call(bus,
1242- g_dbus_connection_get_unique_name(bus),
1243- "/com/canonical/indicator/sound",
1244- "org.gtk.Actions",
1245- "SetState",
1246- g_variant_new("(sva{sv})", "indicator-shown", g_variant_new_boolean(shown), nullptr),
1247- nullptr,
1248- G_DBUS_CALL_FLAGS_NONE,
1249- -1,
1250- nullptr,
1251- nullptr,
1252- nullptr);
1253-
1254- g_clear_object(&bus);
1255- }
1256+ protected:
1257+ DbusTestService * service = NULL;
1258+
1259+ GDBusConnection * session = NULL;
1260+ std::shared_ptr<NotificationsMock> notifications;
1261+
1262+ virtual void SetUp() {
1263+ g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
1264+ g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
1265+
1266+ service = dbus_test_service_new(NULL);
1267+ dbus_test_service_set_bus(service, DBUS_TEST_SERVICE_BUS_SESSION);
1268+
1269+ /* Useful for debugging test failures, not needed all the time (until it fails) */
1270+ #if 0
1271+ auto bustle = std::shared_ptr<DbusTestTask>([]() {
1272+ DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new("notifications-test.bustle"));
1273+ dbus_test_task_set_name(bustle, "Bustle");
1274+ dbus_test_task_set_bus(bustle, DBUS_TEST_SERVICE_BUS_SESSION);
1275+ return bustle;
1276+ }(), [](DbusTestTask * bustle) {
1277+ g_clear_object(&bustle);
1278+ });
1279+ dbus_test_service_add_task(service, bustle.get());
1280+ #endif
1281+
1282+ notifications = std::make_shared<NotificationsMock>();
1283+
1284+ dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1285+ dbus_test_service_start_tasks(service);
1286+
1287+ session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1288+ ASSERT_NE(nullptr, session);
1289+ g_dbus_connection_set_exit_on_close(session, FALSE);
1290+ g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session);
1291+
1292+ /* This is done in main.c */
1293+ notify_init("indicator-sound");
1294+ }
1295+
1296+ virtual void TearDown() {
1297+ if (notify_is_initted())
1298+ notify_uninit();
1299+
1300+ notifications.reset();
1301+ g_clear_object(&service);
1302+
1303+ g_object_unref(session);
1304+
1305+ unsigned int cleartry = 0;
1306+ while (session != NULL && cleartry < 100) {
1307+ loop(100);
1308+ cleartry++;
1309+ }
1310+
1311+ ASSERT_EQ(nullptr, session);
1312+ }
1313+
1314+ static gboolean timeout_cb (gpointer user_data) {
1315+ GMainLoop * loop = static_cast<GMainLoop *>(user_data);
1316+ g_main_loop_quit(loop);
1317+ return G_SOURCE_REMOVE;
1318+ }
1319+
1320+ void loop (unsigned int ms) {
1321+ GMainLoop * loop = g_main_loop_new(NULL, FALSE);
1322+ g_timeout_add(ms, timeout_cb, loop);
1323+ g_main_loop_run(loop);
1324+ g_main_loop_unref(loop);
1325+ }
1326+
1327+ void loop_until(const std::function<bool()>& test, unsigned int max_ms=50, unsigned int test_interval_ms=10) {
1328+
1329+ // g_timeout's callback only allows a single pointer,
1330+ // so use a temporary stack struct to wedge everything into one pointer
1331+ struct CallbackData {
1332+ const std::function<bool()>& test;
1333+ const gint64 deadline;
1334+ GMainLoop* loop = g_main_loop_new(nullptr, false);
1335+ CallbackData (const std::function<bool()>& f, unsigned int max_ms):
1336+ test{f},
1337+ deadline{g_get_monotonic_time() + (max_ms*1000)} {}
1338+ ~CallbackData() {g_main_loop_unref(loop);}
1339+ } data(test, max_ms);
1340+
1341+ // tell the timer to stop looping on success or deadline
1342+ auto timerfunc = [](gpointer gdata) -> gboolean {
1343+ auto& data = *static_cast<CallbackData*>(gdata);
1344+ if (!data.test() && (g_get_monotonic_time() < data.deadline))
1345+ return G_SOURCE_CONTINUE;
1346+ g_main_loop_quit(data.loop);
1347+ return G_SOURCE_REMOVE;
1348+ };
1349+
1350+ // start looping
1351+ g_timeout_add (std::min(max_ms, test_interval_ms), timerfunc, &data);
1352+ g_main_loop_run(data.loop);
1353+ }
1354+
1355+ void loop_until_notifications(unsigned int max_seconds=1) {
1356+ auto test = [this]{ return !notifications->getNotifications().empty(); };
1357+ loop_until(test, max_seconds);
1358+ }
1359+
1360+ static int unref_idle (gpointer user_data) {
1361+ g_variant_unref(static_cast<GVariant *>(user_data));
1362+ return G_SOURCE_REMOVE;
1363+ }
1364+
1365+ std::shared_ptr<MediaPlayerList> playerListMock () {
1366+ auto playerList = std::shared_ptr<MediaPlayerList>(
1367+ MEDIA_PLAYER_LIST(media_player_list_mock_new()),
1368+ [](MediaPlayerList * list) {
1369+ g_clear_object(&list);
1370+ });
1371+ return playerList;
1372+ }
1373+
1374+ std::shared_ptr<IndicatorSoundOptions> optionsMock () {
1375+ auto options = std::shared_ptr<IndicatorSoundOptions>(
1376+ INDICATOR_SOUND_OPTIONS(options_mock_new()),
1377+ [](IndicatorSoundOptions * options){
1378+ g_clear_object(&options);
1379+ });
1380+ return options;
1381+ }
1382+
1383+ std::shared_ptr<VolumeControl> volumeControlMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) {
1384+ auto volumeControl = std::shared_ptr<VolumeControl>(
1385+ VOLUME_CONTROL(volume_control_mock_new(optionsMock.get())),
1386+ [](VolumeControl * control){
1387+ g_clear_object(&control);
1388+ });
1389+ return volumeControl;
1390+ }
1391+
1392+ std::shared_ptr<VolumeWarning> volumeWarningMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) {
1393+ auto volumeWarning = std::shared_ptr<VolumeWarning>(
1394+ VOLUME_WARNING(volume_warning_mock_new(optionsMock.get())),
1395+ [](VolumeWarning * warning){
1396+ g_clear_object(&warning);
1397+ });
1398+ return volumeWarning;
1399+ }
1400+
1401+ std::shared_ptr<IndicatorSoundService> standardService (
1402+ const std::shared_ptr<VolumeControl>& volumeControl,
1403+ const std::shared_ptr<MediaPlayerList>& playerList,
1404+ const std::shared_ptr<IndicatorSoundOptions>& options,
1405+ const std::shared_ptr<VolumeWarning>& warning,
1406+ const std::shared_ptr<AccountsServiceAccess>& accounts_service_access) {
1407+ auto soundService = std::shared_ptr<IndicatorSoundService>(
1408+ indicator_sound_service_new(playerList.get(), volumeControl.get(), nullptr, options.get(), warning.get(), accounts_service_access.get()),
1409+ [](IndicatorSoundService * service){
1410+ g_clear_object(&service);
1411+ });
1412+
1413+ return soundService;
1414+ }
1415+
1416+ void setMockVolume (std::shared_ptr<VolumeControl> volumeControl, double volume, VolumeControlVolumeReasons reason = VOLUME_CONTROL_VOLUME_REASONS_USER_KEYPRESS) {
1417+ VolumeControlVolume * vol = volume_control_volume_new();
1418+ vol->volume = volume;
1419+ vol->reason = reason;
1420+
1421+ volume_control_set_volume(volumeControl.get(), vol);
1422+ g_object_unref(vol);
1423+ }
1424+
1425+ void setIndicatorShown (bool shown) {
1426+ auto bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
1427+
1428+ g_dbus_connection_call(bus,
1429+ g_dbus_connection_get_unique_name(bus),
1430+ "/com/canonical/indicator/sound",
1431+ "org.gtk.Actions",
1432+ "SetState",
1433+ g_variant_new("(sva{sv})", "indicator-shown", g_variant_new_boolean(shown), nullptr),
1434+ nullptr,
1435+ G_DBUS_CALL_FLAGS_NONE,
1436+ -1,
1437+ nullptr,
1438+ nullptr,
1439+ nullptr);
1440+
1441+ g_clear_object(&bus);
1442+ }
1443
1444 };
1445
1446 TEST_F(NotificationsTest, BasicObject) {
1447- auto options = optionsMock();
1448- auto volumeControl = volumeControlMock(options);
1449- auto volumeWarning = volumeWarningMock(options);
1450- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1451-
1452- /* Give some time settle */
1453- loop(50);
1454-
1455- /* Auto free */
1456+ auto options = optionsMock();
1457+ auto volumeControl = volumeControlMock(options);
1458+ auto volumeWarning = volumeWarningMock(options);
1459+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1460+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1461+
1462+ /* Give some time settle */
1463+ loop(50);
1464+
1465+ /* Auto free */
1466 }
1467
1468 TEST_F(NotificationsTest, VolumeChanges) {
1469- auto options = optionsMock();
1470- auto volumeControl = volumeControlMock(options);
1471- auto volumeWarning = volumeWarningMock(options);
1472- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1473-
1474- /* Set a volume */
1475- notifications->clearNotifications();
1476- setMockVolume(volumeControl, 0.50);
1477- loop(50);
1478- auto notev = notifications->getNotifications();
1479- ASSERT_EQ(1, notev.size());
1480- EXPECT_EQ("indicator-sound", notev[0].app_name);
1481- EXPECT_EQ("Volume", notev[0].summary);
1482- EXPECT_EQ(0, notev[0].actions.size());
1483- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
1484- EXPECT_GVARIANT_EQ("@i 50", notev[0].hints["value"]);
1485-
1486- /* Set a different volume */
1487- notifications->clearNotifications();
1488- setMockVolume(volumeControl, 0.60);
1489- loop(50);
1490- notev = notifications->getNotifications();
1491- ASSERT_EQ(1, notev.size());
1492- EXPECT_GVARIANT_EQ("@i 60", notev[0].hints["value"]);
1493-
1494- /* Have pulse set a volume */
1495- notifications->clearNotifications();
1496- setMockVolume(volumeControl, 0.70, VOLUME_CONTROL_VOLUME_REASONS_PULSE_CHANGE);
1497- loop(50);
1498- notev = notifications->getNotifications();
1499- ASSERT_EQ(0, notev.size());
1500-
1501- /* Have AS set the volume */
1502- notifications->clearNotifications();
1503- setMockVolume(volumeControl, 0.80, VOLUME_CONTROL_VOLUME_REASONS_ACCOUNTS_SERVICE_SET);
1504- loop(50);
1505- notev = notifications->getNotifications();
1506- ASSERT_EQ(0, notev.size());
1507+ auto options = optionsMock();
1508+ auto volumeControl = volumeControlMock(options);
1509+ auto volumeWarning = volumeWarningMock(options);
1510+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1511+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1512+
1513+ /* Set a volume */
1514+ notifications->clearNotifications();
1515+ setMockVolume(volumeControl, 0.50);
1516+ loop(50);
1517+ auto notev = notifications->getNotifications();
1518+ ASSERT_EQ(1, notev.size());
1519+ EXPECT_EQ("indicator-sound", notev[0].app_name);
1520+ EXPECT_EQ("Volume", notev[0].summary);
1521+ EXPECT_EQ(0, notev[0].actions.size());
1522+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
1523+ EXPECT_GVARIANT_EQ("@i 50", notev[0].hints["value"]);
1524+
1525+ /* Set a different volume */
1526+ notifications->clearNotifications();
1527+ setMockVolume(volumeControl, 0.60);
1528+ loop(50);
1529+ notev = notifications->getNotifications();
1530+ ASSERT_EQ(1, notev.size());
1531+ EXPECT_GVARIANT_EQ("@i 60", notev[0].hints["value"]);
1532+
1533+ /* Have pulse set a volume */
1534+ notifications->clearNotifications();
1535+ setMockVolume(volumeControl, 0.70, VOLUME_CONTROL_VOLUME_REASONS_PULSE_CHANGE);
1536+ loop(50);
1537+ notev = notifications->getNotifications();
1538+ ASSERT_EQ(0, notev.size());
1539+
1540+ /* Have AS set the volume */
1541+ notifications->clearNotifications();
1542+ setMockVolume(volumeControl, 0.80, VOLUME_CONTROL_VOLUME_REASONS_ACCOUNTS_SERVICE_SET);
1543+ loop(50);
1544+ notev = notifications->getNotifications();
1545+ ASSERT_EQ(0, notev.size());
1546 }
1547
1548 TEST_F(NotificationsTest, StreamChanges) {
1549- auto options = optionsMock();
1550- auto volumeControl = volumeControlMock(options);
1551- auto volumeWarning = volumeWarningMock(options);
1552- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1553-
1554- /* Set a volume */
1555- notifications->clearNotifications();
1556- setMockVolume(volumeControl, 0.5);
1557- loop(50);
1558- auto notev = notifications->getNotifications();
1559- ASSERT_EQ(1, notev.size());
1560-
1561- /* Change Streams, no volume change */
1562- notifications->clearNotifications();
1563- volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM);
1564- setMockVolume(volumeControl, 0.5, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1565- loop(50);
1566- notev = notifications->getNotifications();
1567- EXPECT_EQ(0, notev.size());
1568-
1569- /* Change Streams, volume change */
1570- notifications->clearNotifications();
1571- volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALERT);
1572- setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1573- loop(50);
1574- notev = notifications->getNotifications();
1575- EXPECT_EQ(0, notev.size());
1576-
1577- /* Change Streams, no volume change, volume up */
1578- notifications->clearNotifications();
1579- volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_MULTIMEDIA);
1580- setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1581- loop(50);
1582- setMockVolume(volumeControl, 0.65);
1583- notev = notifications->getNotifications();
1584- EXPECT_EQ(1, notev.size());
1585- EXPECT_GVARIANT_EQ("@i 65", notev[0].hints["value"]);
1586+ auto options = optionsMock();
1587+ auto volumeControl = volumeControlMock(options);
1588+ auto volumeWarning = volumeWarningMock(options);
1589+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1590+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1591+
1592+ /* Set a volume */
1593+ notifications->clearNotifications();
1594+ setMockVolume(volumeControl, 0.5);
1595+ loop(50);
1596+ auto notev = notifications->getNotifications();
1597+ ASSERT_EQ(1, notev.size());
1598+
1599+ /* Change Streams, no volume change */
1600+ notifications->clearNotifications();
1601+ volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM);
1602+ setMockVolume(volumeControl, 0.5, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1603+ loop(50);
1604+ notev = notifications->getNotifications();
1605+ EXPECT_EQ(0, notev.size());
1606+
1607+ /* Change Streams, volume change */
1608+ notifications->clearNotifications();
1609+ volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALERT);
1610+ setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1611+ loop(50);
1612+ notev = notifications->getNotifications();
1613+ EXPECT_EQ(0, notev.size());
1614+
1615+ /* Change Streams, no volume change, volume up */
1616+ notifications->clearNotifications();
1617+ volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_MULTIMEDIA);
1618+ setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE);
1619+ loop(50);
1620+ setMockVolume(volumeControl, 0.65);
1621+ notev = notifications->getNotifications();
1622+ EXPECT_EQ(1, notev.size());
1623+ EXPECT_GVARIANT_EQ("@i 65", notev[0].hints["value"]);
1624 }
1625
1626 TEST_F(NotificationsTest, IconTesting) {
1627- auto options = optionsMock();
1628- auto volumeControl = volumeControlMock(options);
1629- auto volumeWarning = volumeWarningMock(options);
1630- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1631-
1632- /* Set an initial volume */
1633- notifications->clearNotifications();
1634- setMockVolume(volumeControl, 0.5);
1635- loop(50);
1636- auto notev = notifications->getNotifications();
1637- ASSERT_EQ(1, notev.size());
1638-
1639- /* Generate a set of notifications */
1640- notifications->clearNotifications();
1641- for (float i = 0.0; i < 1.01; i += 0.1) {
1642- setMockVolume(volumeControl, i);
1643- }
1644-
1645- loop(50);
1646- notev = notifications->getNotifications();
1647- ASSERT_EQ(11, notev.size());
1648-
1649- EXPECT_EQ("audio-volume-muted", notev[0].app_icon);
1650- EXPECT_EQ("audio-volume-low", notev[1].app_icon);
1651- EXPECT_EQ("audio-volume-low", notev[2].app_icon);
1652- EXPECT_EQ("audio-volume-medium", notev[3].app_icon);
1653- EXPECT_EQ("audio-volume-medium", notev[4].app_icon);
1654- EXPECT_EQ("audio-volume-medium", notev[5].app_icon);
1655- EXPECT_EQ("audio-volume-medium", notev[6].app_icon);
1656- EXPECT_EQ("audio-volume-high", notev[7].app_icon);
1657- EXPECT_EQ("audio-volume-high", notev[8].app_icon);
1658- EXPECT_EQ("audio-volume-high", notev[9].app_icon);
1659- EXPECT_EQ("audio-volume-high", notev[10].app_icon);
1660+ auto options = optionsMock();
1661+ auto volumeControl = volumeControlMock(options);
1662+ auto volumeWarning = volumeWarningMock(options);
1663+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1664+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1665+
1666+ /* Set an initial volume */
1667+ notifications->clearNotifications();
1668+ setMockVolume(volumeControl, 0.5);
1669+ loop(50);
1670+ auto notev = notifications->getNotifications();
1671+ ASSERT_EQ(1, notev.size());
1672+
1673+ /* Generate a set of notifications */
1674+ notifications->clearNotifications();
1675+ for (float i = 0.0; i < 1.01; i += 0.1) {
1676+ setMockVolume(volumeControl, i);
1677+ }
1678+
1679+ loop(50);
1680+ notev = notifications->getNotifications();
1681+ ASSERT_EQ(11, notev.size());
1682+
1683+ EXPECT_EQ("audio-volume-muted", notev[0].app_icon);
1684+ EXPECT_EQ("audio-volume-low", notev[1].app_icon);
1685+ EXPECT_EQ("audio-volume-low", notev[2].app_icon);
1686+ EXPECT_EQ("audio-volume-medium", notev[3].app_icon);
1687+ EXPECT_EQ("audio-volume-medium", notev[4].app_icon);
1688+ EXPECT_EQ("audio-volume-medium", notev[5].app_icon);
1689+ EXPECT_EQ("audio-volume-medium", notev[6].app_icon);
1690+ EXPECT_EQ("audio-volume-high", notev[7].app_icon);
1691+ EXPECT_EQ("audio-volume-high", notev[8].app_icon);
1692+ EXPECT_EQ("audio-volume-high", notev[9].app_icon);
1693+ EXPECT_EQ("audio-volume-high", notev[10].app_icon);
1694 }
1695
1696 TEST_F(NotificationsTest, ServerRestart) {
1697- auto options = optionsMock();
1698- auto volumeControl = volumeControlMock(options);
1699- auto volumeWarning = volumeWarningMock(options);
1700- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1701-
1702- /* Set a volume */
1703- notifications->clearNotifications();
1704- setMockVolume(volumeControl, 0.50);
1705- loop(50);
1706- auto notev = notifications->getNotifications();
1707- ASSERT_EQ(1, notev.size());
1708-
1709- /* Restart server without sync notifications */
1710- notifications->clearNotifications();
1711- dbus_test_service_remove_task(service, (DbusTestTask*)*notifications);
1712- notifications.reset();
1713-
1714- loop(50);
1715-
1716- notifications = std::make_shared<NotificationsMock>(std::vector<std::string>({"body", "body-markup", "icon-static"}));
1717- dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1718- dbus_test_task_run((DbusTestTask*)*notifications);
1719-
1720- /* Change the volume */
1721- notifications->clearNotifications();
1722- setMockVolume(volumeControl, 0.60);
1723- loop(50);
1724- notev = notifications->getNotifications();
1725- ASSERT_EQ(0, notev.size());
1726-
1727- /* Put a good server back */
1728- dbus_test_service_remove_task(service, (DbusTestTask*)*notifications);
1729- notifications.reset();
1730-
1731- loop(50);
1732-
1733- notifications = std::make_shared<NotificationsMock>();
1734- dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1735- dbus_test_task_run((DbusTestTask*)*notifications);
1736-
1737- /* Change the volume again */
1738- notifications->clearNotifications();
1739- setMockVolume(volumeControl, 0.70);
1740- loop(50);
1741- notev = notifications->getNotifications();
1742- ASSERT_EQ(1, notev.size());
1743+ auto options = optionsMock();
1744+ auto volumeControl = volumeControlMock(options);
1745+ auto volumeWarning = volumeWarningMock(options);
1746+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1747+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1748+
1749+ /* Set a volume */
1750+ notifications->clearNotifications();
1751+ setMockVolume(volumeControl, 0.50);
1752+ loop(50);
1753+ auto notev = notifications->getNotifications();
1754+ ASSERT_EQ(1, notev.size());
1755+
1756+ /* Restart server without sync notifications */
1757+ notifications->clearNotifications();
1758+ dbus_test_service_remove_task(service, (DbusTestTask*)*notifications);
1759+ notifications.reset();
1760+
1761+ loop(50);
1762+
1763+ notifications = std::make_shared<NotificationsMock>(std::vector<std::string>({"body", "body-markup", "icon-static"}));
1764+ dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1765+ dbus_test_task_run((DbusTestTask*)*notifications);
1766+
1767+ /* Change the volume */
1768+ notifications->clearNotifications();
1769+ setMockVolume(volumeControl, 0.60);
1770+ loop(50);
1771+ notev = notifications->getNotifications();
1772+ ASSERT_EQ(0, notev.size());
1773+
1774+ /* Put a good server back */
1775+ dbus_test_service_remove_task(service, (DbusTestTask*)*notifications);
1776+ notifications.reset();
1777+
1778+ loop(50);
1779+
1780+ notifications = std::make_shared<NotificationsMock>();
1781+ dbus_test_service_add_task(service, (DbusTestTask*)*notifications);
1782+ dbus_test_task_run((DbusTestTask*)*notifications);
1783+
1784+ /* Change the volume again */
1785+ notifications->clearNotifications();
1786+ setMockVolume(volumeControl, 0.70);
1787+ loop(50);
1788+ notev = notifications->getNotifications();
1789+ ASSERT_EQ(1, notev.size());
1790 }
1791
1792 TEST_F(NotificationsTest, HighVolume) {
1793- auto options = optionsMock();
1794- auto volumeControl = volumeControlMock(options);
1795- auto volumeWarning = volumeWarningMock(options);
1796- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1797-
1798- /* Set a volume */
1799- notifications->clearNotifications();
1800- setMockVolume(volumeControl, 0.50);
1801- loop(50);
1802- auto notev = notifications->getNotifications();
1803- ASSERT_EQ(1, notev.size());
1804- EXPECT_EQ("Volume", notev[0].summary);
1805- EXPECT_EQ("Speakers", notev[0].body);
1806- EXPECT_GVARIANT_EQ("@s 'false'", notev[0].hints["x-canonical-value-bar-tint"]);
1807-
1808- /* Set high volume with volume change */
1809- notifications->clearNotifications();
1810- volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true);
1811- setMockVolume(volumeControl, 0.90);
1812- loop(50);
1813- notev = notifications->getNotifications();
1814- ASSERT_LT(0, notev.size()); /* This passes with one or two since it would just be an update to the first if a second was sent */
1815- EXPECT_EQ("Volume", notev[0].summary);
1816- EXPECT_EQ("Speakers", notev[0].body);
1817- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]);
1818-
1819- /* Move it back */
1820- volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), false);
1821- setMockVolume(volumeControl, 0.50);
1822- loop(50);
1823-
1824- /* Set high volume without level change */
1825- /* NOTE: This can happen if headphones are plugged in */
1826- notifications->clearNotifications();
1827- volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true);
1828- loop(50);
1829- notev = notifications->getNotifications();
1830- ASSERT_EQ(1, notev.size());
1831- EXPECT_EQ("Volume", notev[0].summary);
1832- EXPECT_EQ("Speakers", notev[0].body);
1833- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]);
1834+ auto options = optionsMock();
1835+ auto volumeControl = volumeControlMock(options);
1836+ auto volumeWarning = volumeWarningMock(options);
1837+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1838+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1839+
1840+ /* Set a volume */
1841+ notifications->clearNotifications();
1842+ setMockVolume(volumeControl, 0.50);
1843+ loop(50);
1844+ auto notev = notifications->getNotifications();
1845+ ASSERT_EQ(1, notev.size());
1846+ EXPECT_EQ("Volume", notev[0].summary);
1847+ EXPECT_EQ("Speakers", notev[0].body);
1848+ EXPECT_GVARIANT_EQ("@s 'false'", notev[0].hints["x-canonical-value-bar-tint"]);
1849+
1850+ /* Set high volume with volume change */
1851+ notifications->clearNotifications();
1852+ volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true);
1853+ setMockVolume(volumeControl, 0.90);
1854+ loop(50);
1855+ notev = notifications->getNotifications();
1856+ ASSERT_LT(0, notev.size()); /* This passes with one or two since it would just be an update to the first if a second was sent */
1857+ EXPECT_EQ("Volume", notev[0].summary);
1858+ EXPECT_EQ("Speakers", notev[0].body);
1859+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]);
1860+
1861+ /* Move it back */
1862+ volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), false);
1863+ setMockVolume(volumeControl, 0.50);
1864+ loop(50);
1865+
1866+ /* Set high volume without level change */
1867+ /* NOTE: This can happen if headphones are plugged in */
1868+ notifications->clearNotifications();
1869+ volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true);
1870+ loop(50);
1871+ notev = notifications->getNotifications();
1872+ ASSERT_EQ(1, notev.size());
1873+ EXPECT_EQ("Volume", notev[0].summary);
1874+ EXPECT_EQ("Speakers", notev[0].body);
1875+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]);
1876 }
1877
1878 TEST_F(NotificationsTest, MenuHide) {
1879- auto options = optionsMock();
1880- auto volumeControl = volumeControlMock(options);
1881- auto volumeWarning = volumeWarningMock(options);
1882- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1883-
1884- /* Set a volume */
1885- notifications->clearNotifications();
1886- setMockVolume(volumeControl, 0.50);
1887- loop(50);
1888- auto notev = notifications->getNotifications();
1889- EXPECT_EQ(1, notev.size());
1890-
1891- /* Set the indicator to shown, and set a new volume */
1892- notifications->clearNotifications();
1893- setIndicatorShown(true);
1894- loop(50);
1895- setMockVolume(volumeControl, 0.60);
1896- loop(50);
1897- notev = notifications->getNotifications();
1898- EXPECT_EQ(0, notev.size());
1899-
1900- /* Set the indicator to hidden, and set a new volume */
1901- notifications->clearNotifications();
1902- setIndicatorShown(false);
1903- loop(50);
1904- setMockVolume(volumeControl, 0.70);
1905- loop(50);
1906- notev = notifications->getNotifications();
1907- EXPECT_EQ(1, notev.size());
1908+ auto options = optionsMock();
1909+ auto volumeControl = volumeControlMock(options);
1910+ auto volumeWarning = volumeWarningMock(options);
1911+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1912+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1913+
1914+ /* Set a volume */
1915+ notifications->clearNotifications();
1916+ setMockVolume(volumeControl, 0.50);
1917+ loop(50);
1918+ auto notev = notifications->getNotifications();
1919+ EXPECT_EQ(1, notev.size());
1920+
1921+ /* Set the indicator to shown, and set a new volume */
1922+ notifications->clearNotifications();
1923+ setIndicatorShown(true);
1924+ loop(50);
1925+ setMockVolume(volumeControl, 0.60);
1926+ loop(50);
1927+ notev = notifications->getNotifications();
1928+ EXPECT_EQ(0, notev.size());
1929+
1930+ /* Set the indicator to hidden, and set a new volume */
1931+ notifications->clearNotifications();
1932+ setIndicatorShown(false);
1933+ loop(50);
1934+ setMockVolume(volumeControl, 0.70);
1935+ loop(50);
1936+ notev = notifications->getNotifications();
1937+ EXPECT_EQ(1, notev.size());
1938 }
1939
1940 TEST_F(NotificationsTest, ExtendendVolumeNotification) {
1941- auto options = optionsMock();
1942- auto volumeControl = volumeControlMock(options);
1943- auto volumeWarning = volumeWarningMock(options);
1944- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
1945-
1946- /* Set a volume */
1947- notifications->clearNotifications();
1948- setMockVolume(volumeControl, 0.50);
1949- loop(50);
1950- auto notev = notifications->getNotifications();
1951- ASSERT_EQ(1, notev.size());
1952- EXPECT_EQ("indicator-sound", notev[0].app_name);
1953- EXPECT_EQ("Volume", notev[0].summary);
1954- EXPECT_EQ(0, notev[0].actions.size());
1955- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
1956- EXPECT_GVARIANT_EQ("@i 50", notev[0].hints["value"]);
1957-
1958- /* Allow an amplified volume */
1959- notifications->clearNotifications();
1960- volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM);
1961- options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.5);
1962- loop(50);
1963- notev = notifications->getNotifications();
1964- ASSERT_EQ(1, notev.size());
1965- EXPECT_GVARIANT_EQ("@i 33", notev[0].hints["value"]);
1966-
1967- /* Set to 'over max' */
1968- notifications->clearNotifications();
1969- setMockVolume(volumeControl, 1.525);
1970- loop(50);
1971- notev = notifications->getNotifications();
1972- ASSERT_EQ(1, notev.size());
1973- EXPECT_GVARIANT_EQ("@i 100", notev[0].hints["value"]);
1974-
1975- /* Put back */
1976- notifications->clearNotifications();
1977- options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.0);
1978- loop(50);
1979- notev = notifications->getNotifications();
1980- ASSERT_EQ(1, notev.size());
1981- EXPECT_GVARIANT_EQ("@i 100", notev[0].hints["value"]);
1982+ auto options = optionsMock();
1983+ auto volumeControl = volumeControlMock(options);
1984+ auto volumeWarning = volumeWarningMock(options);
1985+ auto accountsService = std::make_shared<AccountsServiceAccess>();
1986+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
1987+
1988+ /* Set a volume */
1989+ notifications->clearNotifications();
1990+ setMockVolume(volumeControl, 0.50);
1991+ loop(50);
1992+ auto notev = notifications->getNotifications();
1993+ ASSERT_EQ(1, notev.size());
1994+ EXPECT_EQ("indicator-sound", notev[0].app_name);
1995+ EXPECT_EQ("Volume", notev[0].summary);
1996+ EXPECT_EQ(0, notev[0].actions.size());
1997+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
1998+ EXPECT_GVARIANT_EQ("@i 50", notev[0].hints["value"]);
1999+
2000+ /* Allow an amplified volume */
2001+ notifications->clearNotifications();
2002+ volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM);
2003+ options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.5);
2004+ loop(50);
2005+ notev = notifications->getNotifications();
2006+ ASSERT_EQ(1, notev.size());
2007+ EXPECT_GVARIANT_EQ("@i 33", notev[0].hints["value"]);
2008+
2009+ /* Set to 'over max' */
2010+ notifications->clearNotifications();
2011+ setMockVolume(volumeControl, 1.525);
2012+ loop(50);
2013+ notev = notifications->getNotifications();
2014+ ASSERT_EQ(1, notev.size());
2015+ EXPECT_GVARIANT_EQ("@i 100", notev[0].hints["value"]);
2016+
2017+ /* Put back */
2018+ notifications->clearNotifications();
2019+ options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.0);
2020+ loop(50);
2021+ notev = notifications->getNotifications();
2022+ ASSERT_EQ(1, notev.size());
2023+ EXPECT_GVARIANT_EQ("@i 100", notev[0].hints["value"]);
2024 }
2025
2026 TEST_F(NotificationsTest, TriggerWarning) {
2027
2028- // Tests all the conditions needed to trigger a volume warning.
2029- // There are many possible combinations, so this test is slow. :P
2030-
2031- const struct {
2032- bool expected;
2033- VolumeControlActiveOutput output;
2034- } test_outputs[] = {
2035- { false, VOLUME_CONTROL_ACTIVE_OUTPUT_SPEAKERS },
2036- { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HEADPHONES },
2037- { true, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_HEADPHONES },
2038- { false, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_SPEAKER },
2039- { false, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_SPEAKER },
2040- { true, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_HEADPHONES },
2041- { false, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_SPEAKER },
2042- { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_HEADPHONES },
2043- { false, VOLUME_CONTROL_ACTIVE_OUTPUT_CALL_MODE }
2044- };
2045-
2046- const struct {
2047- bool expected;
2048- pa_volume_t volume;
2049- pa_volume_t loud_volume;
2050- } test_volumes[] = {
2051- { false, 50, 100 },
2052- { false, 99, 100 },
2053- { true, 100, 100 },
2054- { true, 101, 100 }
2055- };
2056-
2057- const struct {
2058- bool expected;
2059- bool approved;
2060- } test_approved[] = {
2061- { true, false },
2062- { false, true }
2063- };
2064-
2065- const struct {
2066- bool expected;
2067- bool warnings_enabled;
2068- } test_warnings_enabled[] = {
2069- { true, true },
2070- { false, false }
2071- };
2072-
2073- const struct {
2074- bool expected;
2075- bool multimedia_active;
2076- } test_multimedia_active[] = {
2077- { true, true },
2078- { false, false }
2079- };
2080-
2081- for (const auto& outputs : test_outputs) {
2082- for (const auto& volumes : test_volumes) {
2083- for (const auto& approved : test_approved) {
2084- for (const auto& warnings_enabled : test_warnings_enabled) {
2085- for (const auto& multimedia_active : test_multimedia_active) {
2086-
2087- notifications->clearNotifications();
2088-
2089- // instantiate the test subjects
2090- auto options = optionsMock();
2091- auto volumeControl = volumeControlMock(options);
2092- auto volumeWarning = volumeWarningMock(options);
2093- auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning);
2094-
2095- // run the test
2096- options_mock_mock_set_loud_volume(OPTIONS_MOCK(options.get()), volumes.loud_volume);
2097- options_mock_mock_set_loud_warning_enabled(OPTIONS_MOCK(options.get()), warnings_enabled.warnings_enabled);
2098- volume_warning_mock_set_approved(VOLUME_WARNING_MOCK(volumeWarning.get()), approved.approved);
2099- volume_warning_mock_set_multimedia_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), volumes.volume);
2100- volume_warning_mock_set_multimedia_active(VOLUME_WARNING_MOCK(volumeWarning.get()), multimedia_active.multimedia_active);
2101- volume_control_mock_mock_set_active_output(VOLUME_CONTROL_MOCK(volumeControl.get()), outputs.output);
2102-
2103- loop_until_notifications();
2104-
2105- // check the result
2106- auto notev = notifications->getNotifications();
2107- const bool warning_expected = outputs.expected && volumes.expected && approved.expected && warnings_enabled.expected && multimedia_active.expected;
2108- if (warning_expected) {
2109- EXPECT_TRUE(volume_warning_get_active(volumeWarning.get()));
2110- ASSERT_EQ(1, notev.size());
2111- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-snap-decisions"]);
2112- EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-private-synchronous"]);
2113- }
2114- else {
2115- EXPECT_FALSE(volume_warning_get_active(volumeWarning.get()));
2116- ASSERT_EQ(1, notev.size());
2117- EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-snap-decisions"]);
2118- EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
2119- }
2120-
2121- } // multimedia_active
2122- } // warnings_enabled
2123- } // approved
2124- } // volumes
2125- } // outputs
2126+ // Tests all the conditions needed to trigger a volume warning.
2127+ // There are many possible combinations, so this test is slow. :P
2128+
2129+ const struct {
2130+ bool expected;
2131+ VolumeControlActiveOutput output;
2132+ } test_outputs[] = {
2133+ { false, VOLUME_CONTROL_ACTIVE_OUTPUT_SPEAKERS },
2134+ { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HEADPHONES },
2135+ { true, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_HEADPHONES },
2136+ { false, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_SPEAKER },
2137+ { false, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_SPEAKER },
2138+ { true, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_HEADPHONES },
2139+ { false, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_SPEAKER },
2140+ { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_HEADPHONES },
2141+ { false, VOLUME_CONTROL_ACTIVE_OUTPUT_CALL_MODE }
2142+ };
2143+
2144+ const struct {
2145+ bool expected;
2146+ pa_volume_t volume;
2147+ pa_volume_t loud_volume;
2148+ } test_volumes[] = {
2149+ { false, 50, 100 },
2150+ { false, 99, 100 },
2151+ { true, 100, 100 },
2152+ { true, 101, 100 }
2153+ };
2154+
2155+ const struct {
2156+ bool expected;
2157+ bool approved;
2158+ } test_approved[] = {
2159+ { true, false },
2160+ { false, true }
2161+ };
2162+
2163+ const struct {
2164+ bool expected;
2165+ bool warnings_enabled;
2166+ } test_warnings_enabled[] = {
2167+ { true, true },
2168+ { false, false }
2169+ };
2170+
2171+ const struct {
2172+ bool expected;
2173+ bool multimedia_active;
2174+ } test_multimedia_active[] = {
2175+ { true, true },
2176+ { false, false }
2177+ };
2178+
2179+ for (const auto& outputs : test_outputs) {
2180+ for (const auto& volumes : test_volumes) {
2181+ for (const auto& approved : test_approved) {
2182+ for (const auto& warnings_enabled : test_warnings_enabled) {
2183+ for (const auto& multimedia_active : test_multimedia_active) {
2184+
2185+ notifications->clearNotifications();
2186+
2187+ // instantiate the test subjects
2188+ auto options = optionsMock();
2189+ auto volumeControl = volumeControlMock(options);
2190+ auto volumeWarning = volumeWarningMock(options);
2191+ auto accountsService = std::make_shared<AccountsServiceAccess>();
2192+ auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning, accountsService);
2193+
2194+ // run the test
2195+ options_mock_mock_set_loud_volume(OPTIONS_MOCK(options.get()), volumes.loud_volume);
2196+ options_mock_mock_set_loud_warning_enabled(OPTIONS_MOCK(options.get()), warnings_enabled.warnings_enabled);
2197+ volume_warning_mock_set_approved(VOLUME_WARNING_MOCK(volumeWarning.get()), approved.approved);
2198+ volume_warning_mock_set_multimedia_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), volumes.volume);
2199+ volume_warning_mock_set_multimedia_active(VOLUME_WARNING_MOCK(volumeWarning.get()), multimedia_active.multimedia_active);
2200+ volume_control_mock_mock_set_active_output(VOLUME_CONTROL_MOCK(volumeControl.get()), outputs.output);
2201+
2202+ loop_until_notifications();
2203+
2204+ // check the result
2205+ auto notev = notifications->getNotifications();
2206+ const bool warning_expected = outputs.expected && volumes.expected && approved.expected && warnings_enabled.expected && multimedia_active.expected;
2207+ if (warning_expected) {
2208+ EXPECT_TRUE(volume_warning_get_active(volumeWarning.get()));
2209+ ASSERT_EQ(1, notev.size());
2210+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-snap-decisions"]);
2211+ EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-private-synchronous"]);
2212+ }
2213+ else {
2214+ EXPECT_FALSE(volume_warning_get_active(volumeWarning.get()));
2215+ ASSERT_EQ(1, notev.size());
2216+ EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-snap-decisions"]);
2217+ EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]);
2218+ }
2219+
2220+ } // multimedia_active
2221+ } // warnings_enabled
2222+ } // approved
2223+ } // volumes
2224+ } // outputs
2225 }
2226
2227
2228=== modified file 'tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp'
2229--- tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp 2015-12-23 13:35:46 +0000
2230+++ tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp 2016-03-03 08:59:58 +0000
2231@@ -46,3 +46,18 @@
2232 "Volume",
2233 property("Volume"));
2234 }
2235+
2236+QString AccountsServiceSoundMock::lastRunningPlayer() const
2237+{
2238+ return lastRunningPlayer_;
2239+}
2240+
2241+void AccountsServiceSoundMock::setLastRunningPlayer(QString const & lastRunningPlayer)
2242+{
2243+ lastRunningPlayer_ = lastRunningPlayer;
2244+ notifier_.notifyPropertyChanged(QDBusConnection::systemBus(),
2245+ ACCOUNTS_SOUND_INTERFACE,
2246+ USER_PATH,
2247+ "LastRunningPlayer",
2248+ property("LastRunningPlayer"));
2249+}
2250
2251=== modified file 'tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h'
2252--- tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h 2015-12-23 13:35:46 +0000
2253+++ tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h 2016-03-03 08:59:58 +0000
2254@@ -37,10 +37,13 @@
2255 {
2256 Q_OBJECT
2257 Q_PROPERTY(double Volume READ volume WRITE setVolume)
2258+ Q_PROPERTY(QString LastRunningPlayer READ lastRunningPlayer WRITE setLastRunningPlayer)
2259
2260 public Q_SLOTS:
2261 double volume() const;
2262 void setVolume(double volume);
2263+ QString lastRunningPlayer() const;
2264+ void setLastRunningPlayer(QString const & lastRunningPlayer);
2265
2266 public:
2267 AccountsServiceSoundMock(QObject* parent = 0);
2268@@ -48,6 +51,7 @@
2269
2270 private:
2271 double volume_;
2272+ QString lastRunningPlayer_;
2273 DBusPropertiesNotifier notifier_;
2274 };
2275
2276
2277=== modified file 'tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml'
2278--- tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml 2015-12-23 13:35:46 +0000
2279+++ tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml 2016-03-03 08:59:58 +0000
2280@@ -2,5 +2,6 @@
2281 <node>
2282 <interface name="com.ubuntu.AccountsService.Sound">
2283 <property name="Volume" type="d" access="readwrite"/>
2284+ <property name="LastRunningPlayer" type="s" access="readwrite"/>
2285 </interface>
2286 </node>
2287\ No newline at end of file
2288
2289=== modified file 'tests/sound-menu.cc'
2290--- tests/sound-menu.cc 2016-02-10 13:08:49 +0000
2291+++ tests/sound-menu.cc 2016-03-03 08:59:58 +0000
2292@@ -62,7 +62,7 @@
2293
2294 void check_player_control_buttons(bool canPlay, bool canNext, bool canPrev)
2295 {
2296- SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE, "");
2297+ SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE);
2298
2299 MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url");
2300
2301@@ -96,22 +96,22 @@
2302
2303 /* Player control */
2304 verify_item_attribute(section, 1, "x-canonical-type", g_variant_new_string("com.canonical.unity.playback-item"));
2305- //verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string(""));
2306- if (!canPlay) {
2307+ //verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string(""));
2308+ if (!canPlay) {
2309 verify_item_attribute_is_not_set(section, 1, "x-canonical-play-action", G_VARIANT_TYPE_STRING);
2310- } else {
2311- verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string("indicator.play.player-id"));
2312- }
2313- if (!canNext) {
2314+ } else {
2315+ verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string("indicator.play.player-id"));
2316+ }
2317+ if (!canNext) {
2318 verify_item_attribute_is_not_set(section, 1, "x-canonical-next-action", G_VARIANT_TYPE_STRING);
2319- } else {
2320- verify_item_attribute(section, 1, "x-canonical-next-action", g_variant_new_string("indicator.next.player-id"));
2321- }
2322- if (!canPrev) {
2323+ } else {
2324+ verify_item_attribute(section, 1, "x-canonical-next-action", g_variant_new_string("indicator.next.player-id"));
2325+ }
2326+ if (!canPrev) {
2327 verify_item_attribute_is_not_set(section, 1, "x-canonical-previous-action", G_VARIANT_TYPE_STRING);
2328- } else {
2329- verify_item_attribute(section, 1, "x-canonical-previous-action", g_variant_new_string("indicator.previous.player-id"));
2330- }
2331+ } else {
2332+ verify_item_attribute(section, 1, "x-canonical-previous-action", g_variant_new_string("indicator.previous.player-id"));
2333+ }
2334
2335 g_clear_object(&section);
2336
2337@@ -125,7 +125,7 @@
2338 };
2339
2340 TEST_F(SoundMenuTest, BasicObject) {
2341- SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE, "");
2342+ SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE);
2343
2344 ASSERT_NE(nullptr, menu);
2345
2346@@ -134,7 +134,7 @@
2347 }
2348
2349 TEST_F(SoundMenuTest, AddRemovePlayer) {
2350- SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE, "");
2351+ SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE);
2352
2353 MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url");
2354
2355
2356=== modified file 'tests/volume-control-test.cc'
2357--- tests/volume-control-test.cc 2015-12-29 17:03:41 +0000
2358+++ tests/volume-control-test.cc 2016-03-03 08:59:58 +0000
2359@@ -29,64 +29,65 @@
2360 class VolumeControlTest : public ::testing::Test
2361 {
2362
2363- protected:
2364- DbusTestService * service = NULL;
2365- GDBusConnection * session = NULL;
2366-
2367- virtual void SetUp() override {
2368-
2369- g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
2370- g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
2371-
2372- service = dbus_test_service_new(NULL);
2373- dbus_test_service_start_tasks(service);
2374-
2375- session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2376- ASSERT_NE(nullptr, session);
2377- g_dbus_connection_set_exit_on_close(session, FALSE);
2378- g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session);
2379- }
2380-
2381- virtual void TearDown() {
2382- g_clear_object(&service);
2383-
2384- g_object_unref(session);
2385-
2386- unsigned int cleartry = 0;
2387- while (session != NULL && cleartry < 100) {
2388- loop(100);
2389- cleartry++;
2390- }
2391-
2392- ASSERT_EQ(nullptr, session);
2393- }
2394-
2395- static gboolean timeout_cb (gpointer user_data) {
2396- GMainLoop * loop = static_cast<GMainLoop *>(user_data);
2397- g_main_loop_quit(loop);
2398- return G_SOURCE_REMOVE;
2399- }
2400-
2401- void loop (unsigned int ms) {
2402- GMainLoop * loop = g_main_loop_new(NULL, FALSE);
2403- g_timeout_add(ms, timeout_cb, loop);
2404- g_main_loop_run(loop);
2405- g_main_loop_unref(loop);
2406- }
2407+ protected:
2408+ DbusTestService * service = NULL;
2409+ GDBusConnection * session = NULL;
2410+
2411+ virtual void SetUp() override {
2412+
2413+ g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
2414+ g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
2415+
2416+ service = dbus_test_service_new(NULL);
2417+ dbus_test_service_start_tasks(service);
2418+
2419+ session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2420+ ASSERT_NE(nullptr, session);
2421+ g_dbus_connection_set_exit_on_close(session, FALSE);
2422+ g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session);
2423+ }
2424+
2425+ virtual void TearDown() {
2426+ g_clear_object(&service);
2427+
2428+ g_object_unref(session);
2429+
2430+ unsigned int cleartry = 0;
2431+ while (session != NULL && cleartry < 100) {
2432+ loop(100);
2433+ cleartry++;
2434+ }
2435+
2436+ ASSERT_EQ(nullptr, session);
2437+ }
2438+
2439+ static gboolean timeout_cb (gpointer user_data) {
2440+ GMainLoop * loop = static_cast<GMainLoop *>(user_data);
2441+ g_main_loop_quit(loop);
2442+ return G_SOURCE_REMOVE;
2443+ }
2444+
2445+ void loop (unsigned int ms) {
2446+ GMainLoop * loop = g_main_loop_new(NULL, FALSE);
2447+ g_timeout_add(ms, timeout_cb, loop);
2448+ g_main_loop_run(loop);
2449+ g_main_loop_unref(loop);
2450+ }
2451 };
2452
2453 TEST_F(VolumeControlTest, BasicObject) {
2454- auto options = options_mock_new();
2455- auto pgloop = pa_glib_mainloop_new(NULL);
2456- auto control = volume_control_pulse_new(INDICATOR_SOUND_OPTIONS(options), pgloop);
2457-
2458- /* Setup the PA backend */
2459- loop(100);
2460-
2461- /* Ready */
2462- EXPECT_TRUE(volume_control_get_ready(VOLUME_CONTROL(control)));
2463-
2464- g_clear_object(&control);
2465- g_clear_object(&options);
2466- g_clear_pointer(&pgloop, pa_glib_mainloop_free);
2467+ auto options = options_mock_new();
2468+ auto pgloop = pa_glib_mainloop_new(NULL);
2469+ auto accounts_service_access = accounts_service_access_new();
2470+ auto control = volume_control_pulse_new(INDICATOR_SOUND_OPTIONS(options), pgloop, accounts_service_access);
2471+
2472+ /* Setup the PA backend */
2473+ loop(100);
2474+
2475+ /* Ready */
2476+ EXPECT_TRUE(volume_control_get_ready(VOLUME_CONTROL(control)));
2477+
2478+ g_clear_object(&control);
2479+ g_clear_object(&options);
2480+ g_clear_pointer(&pgloop, pa_glib_mainloop_free);
2481 }

Subscribers

People subscribed via source and target branches