Merge lp:~renatofilho/indicator-datetime/notify-missing-alarm into lp:indicator-datetime/15.10

Proposed by Renato Araujo Oliveira Filho on 2016-04-19
Status: Merged
Approved by: Charles Kerr on 2016-04-29
Approved revision: 472
Merged at revision: 448
Proposed branch: lp:~renatofilho/indicator-datetime/notify-missing-alarm
Merge into: lp:indicator-datetime/15.10
Prerequisite: lp:~renatofilho/indicator-datetime/fix-1533681
Diff against target: 1157 lines (+401/-218)
19 files modified
CMakeLists.txt (+4/-1)
data/CMakeLists.txt (+1/-1)
data/indicator-datetime.desktop.in (+1/-0)
debian/control (+4/-0)
include/datetime/actions-live.h (+13/-10)
include/datetime/actions.h (+4/-8)
include/notifications/haptic.h (+1/-1)
include/notifications/notifications.h (+8/-1)
src/actions-live.cpp (+71/-63)
src/actions.cpp (+34/-58)
src/haptic.cpp (+15/-9)
src/main.cpp (+3/-1)
src/notifications.cpp (+169/-5)
src/snap.cpp (+14/-3)
tests/actions-mock.h (+18/-38)
tests/test-actions.cpp (+8/-8)
tests/test-live-actions.cpp (+31/-9)
tests/test-notification.cpp (+1/-1)
tests/test-sound.cpp (+1/-1)
To merge this branch: bzr merge lp:~renatofilho/indicator-datetime/notify-missing-alarm
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing on 2016-05-02
Charles Kerr (community) 2016-04-19 Approve on 2016-04-29
Review via email: mp+292270@code.launchpad.net

This proposal supersedes a proposal from 2016-04-19.

Commit message

Post message on messaging menu if the notification get timeout.

Description of the change

QA: Jenkins is failing because it is missing the new version of libubuntu-app-launch2-dev (>= 0.9)

To post a comment you must log in.
463. By Renato Araujo Oliveira Filho on 2016-04-19

Fixed crash when clicking on messaging menu.

464. By Renato Araujo Oliveira Filho on 2016-04-20

Fix memory leak on messaging_menu.

465. By Renato Araujo Oliveira Filho on 2016-04-20

Vibrate only once when notification about calendar events.

Charles Kerr (charlesk) wrote :

I have a lot of questions about this.

review: Needs Information

Some small fixes is on the way.

There is some replies inline.

466. By Renato Araujo Oliveira Filho on 2016-04-26

Fixed as reviewer requested.

467. By Renato Araujo Oliveira Filho on 2016-04-26

Make use of G_USEC_PER_SEC.

468. By Renato Araujo Oliveira Filho on 2016-04-27

Update notifications to use the new calendar icon.

469. By Renato Araujo Oliveira Filho on 2016-04-27

Use calendar app icon.

470. By Renato Araujo Oliveira Filho on 2016-04-27

Only creates messaging menu if calendar app is instaled.
Update tests.

Charles Kerr (charlesk) wrote :

More code-level comments inline. Most are minor/suggestions, two NF

A few larger issues:

1. Again I'd /strongly/ prefer that ical events pass in their source desktop id as an x-prop. Not only would that eliminate the need for libubuntu-app-launch2-dev >= 0.9 but it would also let us have per-app sources, eg alarms wouldn't show up under the calendar icon. The approach in this patch works for calendars but assumes that the whole world is a calendar.

2. If we must use libubuntu-app-launch2-dev, can we confirm that it's going to make it into Xenial? It's awfully early to be breaking build compatibility with Xenial

review: Needs Fixing

> More code-level comments inline. Most are minor/suggestions, two NF
>
> A few larger issues:
>
> 1. Again I'd /strongly/ prefer that ical events pass in their source desktop
> id as an x-prop. Not only would that eliminate the need for libubuntu-app-
> launch2-dev >= 0.9 but it would also let us have per-app sources, eg alarms
> wouldn't show up under the calendar icon. The approach in this patch works for
> calendars but assumes that the whole world is a calendar.
We can not guarantee that ical event will have their source id. I can change calendar app to add that, but old events will not have that. Events created by others apps (syncevolution, evolution, thuderbird, etc...) will not set that.
>
> 2. If we must use libubuntu-app-launch2-dev, can we confirm that it's going to
> make it into Xenial? It's awfully early to be breaking build compatibility
> with Xenial

I believe it is already in xenial, ppa builds it ok: https://launchpad.net/~ci-train-ppa-service/+archive/ubuntu/landing-009/+packages

471. By Renato Araujo Oliveira Filho on 2016-04-28

Small fixes requeted by charles during the review.

472. By Renato Araujo Oliveira Filho on 2016-04-28

Detect desktop to launch applications.

Charles Kerr (charlesk) wrote :

Renato, the rest of the changes look fine, any idea why Jenkins is failing?

Charles Kerr (charlesk) wrote :

I love the changes to open_appointment() :-)

> Renato, the rest of the changes look fine, any idea why Jenkins is failing?

Jenkins still missing the new library: Depends: libubuntu-app-launch2-dev (>= 0.9) but it is not going to be installed.

Charles Kerr (charlesk) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-04-28 14:56:46 +0000
3+++ CMakeLists.txt 2016-04-28 14:56:46 +0000
4@@ -48,7 +48,10 @@
5 libnotify>=0.7.6
6 url-dispatcher-1>=1
7 properties-cpp>=0.0.1
8- libaccounts-glib>=1.18)
9+ libaccounts-glib>=1.18
10+ messaging-menu>=12.10
11+ uuid>=2.25
12+ ubuntu-app-launch-2)
13 include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
14
15 ##
16
17=== modified file 'data/CMakeLists.txt'
18--- data/CMakeLists.txt 2015-10-13 15:06:25 +0000
19+++ data/CMakeLists.txt 2016-04-28 14:56:46 +0000
20@@ -52,7 +52,7 @@
21 set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}")
22 configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}")
23
24-# install it
25+# install XDG autostart
26 install (FILES "${XDG_AUTOSTART_FILE}"
27 DESTINATION "${XDG_AUTOSTART_DIR}")
28
29
30=== modified file 'data/indicator-datetime.desktop.in'
31--- data/indicator-datetime.desktop.in 2014-04-04 13:05:59 +0000
32+++ data/indicator-datetime.desktop.in 2016-04-28 14:56:46 +0000
33@@ -7,3 +7,4 @@
34 StartupNotify=false
35 Terminal=false
36 AutostartCondition=GNOME3 unless-session gnome
37+Icon=@messaging_menu_icon@
38
39=== modified file 'debian/control'
40--- debian/control 2016-04-28 14:56:46 +0000
41+++ debian/control 2016-04-28 14:56:46 +0000
42@@ -31,6 +31,10 @@
43 ubuntu-touch-sounds,
44 # for query user e-mails based on accounts
45 libaccounts-glib-dev,
46+# messaging menu integration
47+ libmessaging-menu-dev,
48+ uuid-dev,
49+ libubuntu-app-launch2-dev (>= 0.9),
50 Standards-Version: 3.9.3
51 Homepage: https://launchpad.net/indicator-datetime
52 # If you aren't a member of ~indicator-applet-developers but need to upload
53
54=== modified file 'include/datetime/actions-live.h'
55--- include/datetime/actions-live.h 2016-04-05 21:00:09 +0000
56+++ include/datetime/actions-live.h 2016-04-28 14:56:46 +0000
57@@ -36,26 +36,29 @@
58 class LiveActions: public Actions
59 {
60 public:
61+ enum Desktop {
62+ OTHER = 0,
63+ UNITY7,
64+ UNITY8,
65+ };
66+
67 explicit LiveActions(const std::shared_ptr<State>& state_in);
68 virtual ~LiveActions() =default;
69
70 bool desktop_has_calendar_app() const override;
71- void desktop_open_alarm_app() override;
72- void desktop_open_appointment(const Appointment&, const DateTime&) override;
73- void desktop_open_calendar_app(const DateTime&) override;
74- void desktop_open_settings_app() override;
75-
76- void phone_open_alarm_app() override;
77- void phone_open_appointment(const Appointment&, const DateTime &) override;
78- void phone_open_calendar_app(const DateTime&) override;
79- void phone_open_settings_app() override;
80+ void open_alarm_app() override;
81+ void open_appointment(const Appointment&, const DateTime&) override;
82+ void open_calendar_app(const DateTime&) override;
83+ void open_settings_app() override;
84
85 void set_location(const std::string& zone, const std::string& name) override;
86
87 protected:
88- static bool is_unity();
89+ virtual Desktop get_desktop();
90 virtual void execute_command(const std::string& command);
91 virtual void dispatch_url(const std::string& url);
92+
93+ void unity8_open_appointment(const Appointment& appt, const DateTime& date);
94 };
95
96 } // namespace datetime
97
98=== modified file 'include/datetime/actions.h'
99--- include/datetime/actions.h 2016-03-21 17:32:39 +0000
100+++ include/datetime/actions.h 2016-04-28 14:56:46 +0000
101@@ -44,15 +44,11 @@
102 public:
103
104 virtual bool desktop_has_calendar_app() const =0;
105- virtual void desktop_open_alarm_app() =0;
106- virtual void desktop_open_appointment(const Appointment&, const DateTime&) =0;
107- virtual void desktop_open_calendar_app(const DateTime&) =0;
108- virtual void desktop_open_settings_app() =0;
109
110- virtual void phone_open_alarm_app() =0;
111- virtual void phone_open_appointment(const Appointment&, const DateTime&) =0;
112- virtual void phone_open_calendar_app(const DateTime&) =0;
113- virtual void phone_open_settings_app() =0;
114+ virtual void open_alarm_app() =0;
115+ virtual void open_appointment(const Appointment&, const DateTime&) =0;
116+ virtual void open_calendar_app(const DateTime&) =0;
117+ virtual void open_settings_app() =0;
118
119 virtual void set_location(const std::string& zone, const std::string& name)=0;
120
121
122=== modified file 'include/notifications/haptic.h'
123--- include/notifications/haptic.h 2014-09-17 16:51:51 +0000
124+++ include/notifications/haptic.h 2016-04-28 14:56:46 +0000
125@@ -41,7 +41,7 @@
126 MODE_PULSE
127 };
128
129- explicit Haptic(const Mode& mode = MODE_PULSE);
130+ explicit Haptic(const Mode& mode = MODE_PULSE, bool repeat = false);
131 ~Haptic();
132
133 private:
134
135=== modified file 'include/notifications/notifications.h'
136--- include/notifications/notifications.h 2014-09-19 14:35:07 +0000
137+++ include/notifications/notifications.h 2016-04-28 14:56:46 +0000
138@@ -50,6 +50,8 @@
139
140 void set_icon_name (const std::string& icon_name);
141
142+ void set_start_time(uint64_t time);
143+
144 /* Set an interval, after which the notification will automatically
145 be closed. If not set, the notification server's default timeout
146 is used. */
147@@ -62,15 +64,20 @@
148 static constexpr char const * HINT_NONSHAPED_ICON {"x-canonical-non-shaped-icon"};
149 static constexpr char const * HINT_AFFIRMATIVE_HINT {"x-canonical-private-affirmative-tint"};
150 static constexpr char const * HINT_REJECTION_TINT {"x-canonical-private-rejection-tint"};
151+ static constexpr char const * HINT_INTERACTIVE {"x-canonical-switch-to-application"};
152
153 /* Add an action button.
154 This may fail if the Engine doesn't support actions.
155 @see Engine::supports_actions() */
156 void add_action (const std::string& action, const std::string& label);
157
158- /** Sets the closed callback. This will be called exactly once. */
159+ /** Sets the closed callback. This will be called exactly once. After notification dissapear */
160 void set_closed_callback (std::function<void(const std::string& action)>);
161
162+ /** Sets the time-out callback. This will be called exactly once. */
163+ void set_timeout_callback (std::function<void()>);
164+
165+
166 private:
167 friend class Engine;
168 class Impl;
169
170=== modified file 'src/actions-live.cpp'
171--- src/actions-live.cpp 2016-04-10 00:03:57 +0000
172+++ src/actions-live.cpp 2016-04-28 14:56:46 +0000
173@@ -62,48 +62,94 @@
174 ****
175 ***/
176
177-bool LiveActions::is_unity()
178+LiveActions::Desktop LiveActions::get_desktop()
179 {
180 static bool cached = false;
181- static bool result;
182+ static LiveActions::Desktop result = LiveActions::OTHER;
183
184 if (cached) {
185 return result;
186 }
187
188- result = false;
189- const gchar *xdg_current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
190-
191- if (xdg_current_desktop != NULL) {
192- gchar **desktop_names = g_strsplit (xdg_current_desktop, ":", 0);
193- for (size_t i = 0; desktop_names[i]; ++i) {
194- if (!g_strcmp0 (desktop_names[i], "Unity")) {
195- result = true;
196- break;
197+ // check for unity8
198+ if (g_getenv ("MIR_SOCKET") != nullptr) {
199+ result = LiveActions::UNITY8;
200+ } else {
201+ const gchar *xdg_current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
202+ if (xdg_current_desktop != NULL) {
203+ gchar **desktop_names = g_strsplit (xdg_current_desktop, ":", 0);
204+ for (size_t i = 0; desktop_names[i]; ++i) {
205+ if (!g_strcmp0 (desktop_names[i], "Unity")) {
206+ result = LiveActions::UNITY7;
207+ break;
208+ }
209 }
210+ g_strfreev (desktop_names);
211 }
212- g_strfreev (desktop_names);
213 }
214 cached = true;
215 return result;
216 }
217
218-void LiveActions::desktop_open_settings_app()
219-{
220- if (g_getenv ("MIR_SOCKET") != nullptr)
221- {
222+void LiveActions::open_alarm_app()
223+{
224+ switch(get_desktop()) {
225+ case LiveActions::UNITY8:
226+ dispatch_url("appid://com.ubuntu.clock/clock/current-user-version");
227+ break;
228+ case LiveActions::UNITY7:
229+ default:
230+ execute_command("evolution -c calendar");
231+ }
232+}
233+
234+void LiveActions::open_appointment(const Appointment& appt, const DateTime& date)
235+{
236+ switch(get_desktop()) {
237+ case LiveActions::UNITY8:
238+ unity8_open_appointment(appt, date);
239+ break;
240+ case LiveActions::UNITY7:
241+ default:
242+ open_calendar_app(date);
243+ }
244+}
245+
246+void LiveActions::open_calendar_app(const DateTime& dt)
247+{
248+ switch(get_desktop()) {
249+ case LiveActions::UNITY8:
250+ {
251+ const auto utc = dt.to_timezone("UTC");
252+ auto cmd = utc.format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
253+ dispatch_url(cmd);
254+ break;
255+ }
256+ case LiveActions::UNITY7:
257+ default:
258+ {
259+ const auto utc = dt.start_of_day().to_timezone("UTC");
260+ auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
261+ execute_command(cmd.c_str());
262+ }
263+ }
264+}
265+
266+void LiveActions::open_settings_app()
267+{
268+ switch(get_desktop()) {
269+ case LiveActions::UNITY8:
270 dispatch_url("settings:///system/time-date");
271- }
272- else if (is_unity())
273- {
274+ break;
275+ case LiveActions::UNITY7:
276 execute_command("unity-control-center datetime");
277- }
278- else
279- {
280+ break;
281+ default:
282 execute_command("gnome-control-center datetime");
283 }
284 }
285
286+
287 bool LiveActions::desktop_has_calendar_app() const
288 {
289 static bool inited = false;
290@@ -136,33 +182,7 @@
291 return have_calendar;
292 }
293
294-void LiveActions::desktop_open_alarm_app()
295-{
296- execute_command("evolution -c calendar");
297-}
298-
299-void LiveActions::desktop_open_appointment(const Appointment&, const DateTime& date)
300-{
301- desktop_open_calendar_app(date);
302-}
303-
304-void LiveActions::desktop_open_calendar_app(const DateTime& dt)
305-{
306- const auto utc = dt.start_of_day().to_timezone("UTC");
307- auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
308- execute_command(cmd.c_str());
309-}
310-
311-/***
312-****
313-***/
314-
315-void LiveActions::phone_open_alarm_app()
316-{
317- dispatch_url("appid://com.ubuntu.clock/clock/current-user-version");
318-}
319-
320-void LiveActions::phone_open_appointment(const Appointment& appt, const DateTime& date)
321+void LiveActions::unity8_open_appointment(const Appointment& appt, const DateTime& date)
322 {
323 if (!appt.activation_url.empty())
324 {
325@@ -171,28 +191,16 @@
326 else switch (appt.type)
327 {
328 case Appointment::UBUNTU_ALARM:
329- phone_open_alarm_app();
330+ open_alarm_app();
331 break;
332
333 case Appointment::EVENT:
334 default:
335- phone_open_calendar_app(date);
336+ open_calendar_app(date);
337 break;
338 }
339 }
340
341-void LiveActions::phone_open_calendar_app(const DateTime& dt)
342-{
343- const auto utc = dt.to_timezone("UTC");
344- auto cmd = utc.format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
345- dispatch_url(cmd);
346-}
347-
348-void LiveActions::phone_open_settings_app()
349-{
350- dispatch_url("settings:///system/time-date");
351-}
352-
353 /***
354 ****
355 ***/
356
357=== modified file 'src/actions.cpp'
358--- src/actions.cpp 2016-03-21 17:32:39 +0000
359+++ src/actions.cpp 2016-04-28 14:56:46 +0000
360@@ -62,52 +62,28 @@
361 return false;
362 }
363
364-void on_desktop_appointment_activated (GSimpleAction*, GVariant *vdata, gpointer gself)
365-{
366- auto self = static_cast<Actions*>(gself);
367- Appointment appt;
368- const gchar* uid = nullptr;
369- gint64 time = 0;
370- g_variant_get(vdata, "(&sx)", &uid, &time);
371- if (lookup_appointment_by_uid(self->state(), uid, appt))
372- self->desktop_open_appointment(appt, DateTime::Local(time));
373-}
374-void on_desktop_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
375-{
376- static_cast<Actions*>(gself)->desktop_open_alarm_app();
377-}
378-void on_desktop_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
379-{
380- const auto dt = datetime_from_timet_variant(vt);
381- static_cast<Actions*>(gself)->desktop_open_calendar_app(dt);
382-}
383-void on_desktop_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
384-{
385- static_cast<Actions*>(gself)->desktop_open_settings_app();
386-}
387-
388-void on_phone_appointment_activated (GSimpleAction*, GVariant *vdata, gpointer gself)
389-{
390- auto self = static_cast<Actions*>(gself);
391- Appointment appt;
392- const gchar* uid = nullptr;
393- gint64 time = 0;
394- g_variant_get(vdata, "(&sx)", &uid, &time);
395- if (lookup_appointment_by_uid(self->state(), uid, appt))
396- self->phone_open_appointment(appt, DateTime::Local(time));
397-}
398-void on_phone_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
399-{
400- static_cast<Actions*>(gself)->phone_open_alarm_app();
401-}
402-void on_phone_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
403-{
404- const auto dt = datetime_from_timet_variant(vt);
405- static_cast<Actions*>(gself)->phone_open_calendar_app(dt);
406-}
407-void on_phone_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
408-{
409- static_cast<Actions*>(gself)->phone_open_settings_app();
410+void on_appointment_activated (GSimpleAction*, GVariant *vdata, gpointer gself)
411+{
412+ auto self = static_cast<Actions*>(gself);
413+ Appointment appt;
414+ const gchar* uid = nullptr;
415+ gint64 time = 0;
416+ g_variant_get(vdata, "(&sx)", &uid, &time);
417+ if (lookup_appointment_by_uid(self->state(), uid, appt))
418+ self->open_appointment(appt, DateTime::Local(time));
419+}
420+void on_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
421+{
422+ static_cast<Actions*>(gself)->open_alarm_app();
423+}
424+void on_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
425+{
426+ const auto dt = datetime_from_timet_variant(vt);
427+ static_cast<Actions*>(gself)->open_calendar_app(dt);
428+}
429+void on_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
430+{
431+ static_cast<Actions*>(gself)->open_settings_app();
432 }
433
434
435@@ -136,9 +112,9 @@
436 }
437 }
438
439-void on_calendar_activated(GSimpleAction * /*action*/,
440- GVariant * state,
441- gpointer gself)
442+void on_calendar_date_activated(GSimpleAction * /*action*/,
443+ GVariant * state,
444+ gpointer gself)
445 {
446 const time_t t = g_variant_get_int64(state);
447
448@@ -200,15 +176,15 @@
449 {
450 GActionEntry entries[] = {
451
452- { "desktop.open-appointment", on_desktop_appointment_activated, "(sx)", nullptr },
453- { "desktop.open-alarm-app", on_desktop_alarm_activated },
454- { "desktop.open-calendar-app", on_desktop_calendar_activated, "x", nullptr },
455- { "desktop.open-settings-app", on_desktop_settings_activated },
456+ { "desktop.open-appointment", on_appointment_activated, "(sx)", nullptr },
457+ { "desktop.open-alarm-app", on_alarm_activated },
458+ { "desktop.open-calendar-app", on_calendar_activated, "x", nullptr },
459+ { "desktop.open-settings-app", on_settings_activated },
460
461- { "phone.open-appointment", on_phone_appointment_activated, "(sx)", nullptr },
462- { "phone.open-alarm-app", on_phone_alarm_activated },
463- { "phone.open-calendar-app", on_phone_calendar_activated, "x", nullptr },
464- { "phone.open-settings-app", on_phone_settings_activated },
465+ { "phone.open-appointment", on_appointment_activated, "(sx)", nullptr },
466+ { "phone.open-alarm-app", on_alarm_activated },
467+ { "phone.open-calendar-app", on_calendar_activated, "x", nullptr },
468+ { "phone.open-settings-app", on_settings_activated },
469
470 { "calendar-active", nullptr, nullptr, "false", on_calendar_active_changed },
471 { "set-location", on_set_location, "s" }
472@@ -242,7 +218,7 @@
473 v = create_calendar_state(state);
474 a = g_simple_action_new_stateful("calendar", G_VARIANT_TYPE_INT64, v);
475 g_action_map_add_action(gam, G_ACTION(a));
476- g_signal_connect(a, "activate", G_CALLBACK(on_calendar_activated), this);
477+ g_signal_connect(a, "activate", G_CALLBACK(on_calendar_date_activated), this);
478 g_object_unref(a);
479
480 ///
481
482=== modified file 'src/haptic.cpp'
483--- src/haptic.cpp 2014-08-01 00:52:42 +0000
484+++ src/haptic.cpp 2016-04-28 14:56:46 +0000
485@@ -37,9 +37,10 @@
486 {
487 public:
488
489- Impl(const Mode& mode):
490+ Impl(const Mode& mode, bool repeat):
491 m_mode(mode),
492- m_cancellable(g_cancellable_new())
493+ m_cancellable(g_cancellable_new()),
494+ m_repeat(repeat)
495 {
496 g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready, this);
497 }
498@@ -93,11 +94,15 @@
499 // one second on, one second off.
500 m_pattern = std::vector<uint32_t>({1000u, 1000u});
501 break;
502- }
503-
504- // Set up a loop to keep repeating the pattern
505- auto msec = std::accumulate(m_pattern.begin(), m_pattern.end(), 0u);
506- m_tag = g_timeout_add(msec, call_vibrate_pattern_static, this);
507+
508+ }
509+
510+ if (m_repeat)
511+ {
512+ // Set up a loop to keep repeating the pattern
513+ auto msec = std::accumulate(m_pattern.begin(), m_pattern.end(), 0u);
514+ m_tag = g_timeout_add(msec, call_vibrate_pattern_static, this);
515+ }
516 call_vibrate_pattern();
517 }
518
519@@ -146,14 +151,15 @@
520 GDBusConnection * m_bus = nullptr;
521 std::vector<uint32_t> m_pattern;
522 guint m_tag = 0;
523+ bool m_repeat = false;
524 };
525
526 /***
527 ****
528 ***/
529
530-Haptic::Haptic(const Mode& mode):
531- impl(new Impl (mode))
532+Haptic::Haptic(const Mode& mode, bool repeat):
533+ impl(new Impl (mode, repeat))
534 {
535 }
536
537
538=== modified file 'src/main.cpp'
539--- src/main.cpp 2016-04-28 14:56:46 +0000
540+++ src/main.cpp 2016-04-28 14:56:46 +0000
541@@ -152,7 +152,9 @@
542 auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) {
543 snooze_planner->add(appointment, alarm);
544 };
545- auto on_ok = [](const Appointment&, const Alarm&){};
546+ auto on_ok = [actions](const Appointment& app, const Alarm&){
547+ actions->open_appointment(app, app.begin);
548+ };
549 auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) {
550 (*snap)(appointment, alarm, on_snooze, on_ok);
551 engine->disable_ubuntu_alarm(appointment);
552
553=== modified file 'src/notifications.cpp'
554--- src/notifications.cpp 2014-09-17 17:08:01 +0000
555+++ src/notifications.cpp 2016-04-28 14:56:46 +0000
556@@ -21,10 +21,21 @@
557
558 #include <libnotify/notify.h>
559
560+#include <messaging-menu/messaging-menu-app.h>
561+#include <messaging-menu/messaging-menu-message.h>
562+
563+#include <libubuntu-app-launch-2/ubuntu-app-launch/appid.h>
564+
565+#include <uuid/uuid.h>
566+
567+#include <gio/gdesktopappinfo.h>
568+
569 #include <map>
570 #include <set>
571 #include <string>
572 #include <vector>
573+#include <memory>
574+
575
576 namespace unity {
577 namespace indicator {
578@@ -45,9 +56,11 @@
579 std::string m_body;
580 std::string m_icon_name;
581 std::chrono::seconds m_duration;
582+ gint64 m_start_time {};
583 std::set<std::string> m_string_hints;
584 std::vector<std::pair<std::string,std::string>> m_actions;
585 std::function<void(const std::string&)> m_closed_callback;
586+ std::function<void()> m_timeout_callback;
587 };
588
589 Builder::Builder():
590@@ -101,6 +114,18 @@
591 impl->m_closed_callback.swap (cb);
592 }
593
594+void
595+Builder::set_timeout_callback (std::function<void()> cb)
596+{
597+ impl->m_timeout_callback.swap (cb);
598+}
599+
600+void
601+Builder::set_start_time (uint64_t time)
602+{
603+ impl->m_start_time = time;
604+}
605+
606 /***
607 ****
608 ***/
609@@ -110,7 +135,14 @@
610 struct notification_data
611 {
612 std::shared_ptr<NotifyNotification> nn;
613- std::function<void(const std::string&)> closed_callback;
614+ Builder::Impl data;
615+ };
616+
617+ struct messaging_menu_data
618+ {
619+ std::string msg_id;
620+ std::function<void()> callback;
621+ Engine::Impl *self;
622 };
623
624 public:
625@@ -120,13 +152,23 @@
626 {
627 if (!notify_init(app_name.c_str()))
628 g_critical("Unable to initialize libnotify!");
629+
630+ // messaging menu
631+ auto app_id = calendar_app_id();
632+ if (!app_id.empty()) {
633+ m_messaging_app.reset(messaging_menu_app_new(app_id.c_str()), g_object_unref);
634+ messaging_menu_app_register(m_messaging_app.get());
635+ }
636 }
637
638 ~Impl()
639 {
640 close_all ();
641+ remove_all ();
642
643 notify_uninit ();
644+ if (m_messaging_app)
645+ messaging_menu_app_unregister (m_messaging_app.get());
646 }
647
648 const std::string& app_name() const
649@@ -217,7 +259,7 @@
650 notification_key_quark(),
651 GINT_TO_POINTER(key));
652
653- m_notifications[key] = { nn, info.m_closed_callback };
654+ m_notifications[key] = { nn, info };
655 g_signal_connect (nn.get(), "closed",
656 G_CALLBACK(on_notification_closed), this);
657
658@@ -238,6 +280,72 @@
659 return ret;
660 }
661
662+ std::string post(const Builder::Impl& data)
663+ {
664+ if (!m_messaging_app) {
665+ return std::string();
666+ }
667+ uuid_t message_uuid;
668+ uuid_generate(message_uuid);
669+
670+ char uuid_buf[37];
671+ uuid_unparse(message_uuid, uuid_buf);
672+ const std::string message_id(uuid_buf);
673+
674+ // use full icon path name, "calendar-app" does not work with themed icons
675+ auto icon_file = g_file_new_for_path(calendar_app_icon().c_str());
676+ // messaging_menu_message_new: will take control of icon object
677+ GIcon *icon = g_file_icon_new(icon_file);
678+ g_object_unref(icon_file);
679+
680+ // check if source exists
681+ if (!messaging_menu_app_has_source(m_messaging_app.get(), m_app_name.c_str()))
682+ messaging_menu_app_append_source(m_messaging_app.get(), m_app_name.c_str(), nullptr, "Calendar");
683+
684+ auto msg = messaging_menu_message_new(message_id.c_str(),
685+ icon,
686+ data.m_title.c_str(),
687+ nullptr,
688+ data.m_body.c_str(),
689+ data.m_start_time * G_USEC_PER_SEC); // secs -> microsecs
690+ if (msg)
691+ {
692+ std::shared_ptr<messaging_menu_data> msg_data(new messaging_menu_data{message_id, data.m_timeout_callback, this});
693+ m_messaging_messages[message_id] = msg_data;
694+ g_signal_connect(G_OBJECT(msg), "activate",
695+ G_CALLBACK(on_message_activated), msg_data.get());
696+ messaging_menu_app_append_message(m_messaging_app.get(), msg, m_app_name.c_str(), false);
697+
698+ // we use that to keep track of messaging, in case of message get cleared from menu
699+ g_object_set_data_full(G_OBJECT(msg), "destroy-notify", msg_data.get(), on_message_destroyed);
700+ // keep the message control with message_menu
701+ g_object_unref(msg);
702+
703+ return message_id;
704+ } else {
705+ g_warning("Fail to create messaging menu message");
706+ }
707+ return "";
708+ }
709+
710+ void remove (const std::string &key)
711+ {
712+ auto it = m_messaging_messages.find(key);
713+ if (it != m_messaging_messages.end())
714+ {
715+ // tell the server to remove message
716+ messaging_menu_app_remove_message_by_id(m_messaging_app.get(), it->second->msg_id.c_str());
717+ // message will be remove by on_message_destroyed cb.
718+ }
719+ }
720+
721+ void remove_all ()
722+ {
723+ // call remove() on all our keys
724+ while (!m_messaging_messages.empty())
725+ remove(m_messaging_messages.begin()->first);
726+ }
727+
728 private:
729
730 const std::set<std::string>& server_caps() const
731@@ -279,6 +387,28 @@
732 static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey));
733 }
734
735+ static void on_message_activated (MessagingMenuMessage *,
736+ const char *,
737+ GVariant *,
738+ gpointer data)
739+ {
740+ auto msg_data = static_cast<messaging_menu_data*>(data);
741+ auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id);
742+ g_return_if_fail (it != msg_data->self->m_messaging_messages.end());
743+ const auto& ndata = it->second;
744+
745+ if (ndata->callback)
746+ ndata->callback();
747+ }
748+
749+ static void on_message_destroyed(gpointer data)
750+ {
751+ auto msg_data = static_cast<messaging_menu_data*>(data);
752+ auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id);
753+ if (it != msg_data->self->m_messaging_messages.end())
754+ msg_data->self->m_messaging_messages.erase(it);
755+ }
756+
757 void remove_closed_notification (int key)
758 {
759 auto it = m_notifications.find(key);
760@@ -286,25 +416,59 @@
761
762 const auto& ndata = it->second;
763 auto nn = ndata.nn.get();
764- if (ndata.closed_callback)
765+
766+ if (ndata.data.m_closed_callback)
767 {
768 std::string action;
769-
770 const GQuark q = notification_action_quark();
771 const gpointer p = g_object_get_qdata(G_OBJECT(nn), q);
772 if (p != nullptr)
773 action = static_cast<const char*>(p);
774
775- ndata.closed_callback (action);
776+ ndata.data.m_closed_callback (action);
777+ // empty action means that the notification got timeout
778+ // post a message on messaging menu
779+ if (action.empty())
780+ post(ndata.data);
781 }
782
783 m_notifications.erase(it);
784 }
785
786+ static std::string calendar_app_id()
787+ {
788+ auto app_id = ubuntu::app_launch::AppID::discover("com.ubuntu.calendar");
789+ if (!app_id.empty())
790+ // Due the use of old API by messaging_menu we need append a extra ".desktop" to the app_id.
791+ return std::string(app_id) + ".desktop";
792+ else
793+ return std::string();
794+ }
795+
796+ static std::string calendar_app_icon()
797+ {
798+ auto app_desktop = g_desktop_app_info_new(calendar_app_id().c_str());
799+ if (app_desktop != nullptr) {
800+ auto icon_name = g_desktop_app_info_get_string(app_desktop, "Icon");
801+ g_object_unref(app_desktop);
802+ if (icon_name) {
803+ std::string result(icon_name);
804+ g_free(icon_name);
805+ return result;
806+ }
807+ }
808+ g_warning("Fail to get calendar icon");
809+ return std::string();
810+ }
811+
812 /***
813 ****
814 ***/
815
816+ // messaging menu
817+ std::shared_ptr<MessagingMenuApp> m_messaging_app;
818+ std::map<std::string, std::shared_ptr<messaging_menu_data> > m_messaging_messages;
819+
820 const std::string m_app_name;
821
822 // key-to-data
823
824=== modified file 'src/snap.cpp'
825--- src/snap.cpp 2016-04-28 14:56:46 +0000
826+++ src/snap.cpp 2016-04-28 14:56:46 +0000
827@@ -116,15 +116,16 @@
828 if (should_vibrate()) {
829 const auto haptic_mode = m_settings->alarm_haptic.get();
830 if (haptic_mode == "pulse")
831- haptic = std::make_shared<uin::Haptic>(uin::Haptic::MODE_PULSE);
832+ haptic = std::make_shared<uin::Haptic>(uin::Haptic::MODE_PULSE, appointment.is_ubuntu_alarm());
833 }
834
835 // show a notification...
836 const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get());
837 uin::Builder b;
838 b.set_body (appointment.summary);
839- b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "reminder");
840+ b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "calendar-app");
841 b.add_hint (uin::Builder::HINT_NONSHAPED_ICON);
842+ b.set_start_time (appointment.begin.to_unix());
843
844 const char * timefmt;
845 if (is_locale_12h()) {
846@@ -150,6 +151,9 @@
847 b.add_hint (uin::Builder::HINT_AFFIRMATIVE_HINT);
848 b.add_action ("ok", _("OK"));
849 b.add_action ("snooze", _("Snooze"));
850+ } else {
851+ b.add_hint (uin::Builder::HINT_INTERACTIVE);
852+ b.add_action ("ok", _("OK"));
853 }
854
855 // add 'sound', 'haptic', and 'awake' objects to the capture so
856@@ -159,10 +163,17 @@
857 (const std::string& action){
858 if (action == "snooze")
859 snooze(appointment, alarm);
860- else
861+ else if (action == "ok")
862 ok(appointment, alarm);
863 });
864
865+ //TODO: we need to extend it to support alarms appoitments
866+ if (!appointment.is_ubuntu_alarm()) {
867+ b.set_timeout_callback([appointment, alarm, ok](){
868+ ok(appointment, alarm);
869+ });
870+ }
871+
872 const auto key = m_engine->show(b);
873 if (key)
874 m_notifications.insert (key);
875
876=== modified file 'tests/actions-mock.h'
877--- tests/actions-mock.h 2016-03-21 17:32:39 +0000
878+++ tests/actions-mock.h 2016-04-28 14:56:46 +0000
879@@ -34,14 +34,10 @@
880 explicit MockActions(const std::shared_ptr<State>& state_in): Actions(state_in) {}
881 ~MockActions() =default;
882
883- enum Action { DesktopOpenAlarmApp,
884- DesktopOpenAppt,
885- DesktopOpenCalendarApp,
886- DesktopOpenSettingsApp,
887- PhoneOpenAlarmApp,
888- PhoneOpenAppt,
889- PhoneOpenCalendarApp,
890- PhoneOpenSettingsApp,
891+ enum Action { OpenAlarmApp,
892+ OpenAppt,
893+ OpenCalendarApp,
894+ OpenSettingsApp,
895 SetLocation };
896
897 const std::vector<Action>& history() const { return m_history; }
898@@ -54,36 +50,20 @@
899 bool desktop_has_calendar_app() const {
900 return m_desktop_has_calendar_app;
901 }
902- void desktop_open_alarm_app() {
903- m_history.push_back(DesktopOpenAlarmApp);
904- }
905- void desktop_open_appointment(const Appointment& appt, const DateTime& dt) {
906- m_appt = appt;
907- m_date_time = dt;
908- m_history.push_back(DesktopOpenAppt);
909- }
910- void desktop_open_calendar_app(const DateTime& dt) {
911- m_date_time = dt;
912- m_history.push_back(DesktopOpenCalendarApp);
913- }
914- void desktop_open_settings_app() {
915- m_history.push_back(DesktopOpenSettingsApp);
916- }
917-
918- void phone_open_alarm_app() {
919- m_history.push_back(PhoneOpenAlarmApp);
920- }
921- void phone_open_appointment(const Appointment& appt, const DateTime& dt) {
922- m_appt = appt;
923- m_date_time = dt;
924- m_history.push_back(PhoneOpenAppt);
925- }
926- void phone_open_calendar_app(const DateTime& dt) {
927- m_date_time = dt;
928- m_history.push_back(PhoneOpenCalendarApp);
929- }
930- void phone_open_settings_app() {
931- m_history.push_back(PhoneOpenSettingsApp);
932+ void open_alarm_app() {
933+ m_history.push_back(OpenAlarmApp);
934+ }
935+ void open_appointment(const Appointment& appt, const DateTime& dt) {
936+ m_appt = appt;
937+ m_date_time = dt;
938+ m_history.push_back(OpenAppt);
939+ }
940+ void open_calendar_app(const DateTime& dt) {
941+ m_date_time = dt;
942+ m_history.push_back(OpenCalendarApp);
943+ }
944+ void open_settings_app() {
945+ m_history.push_back(OpenSettingsApp);
946 }
947
948 void set_location(const std::string& zone_, const std::string& name_) {
949
950=== modified file 'tests/test-actions.cpp'
951--- tests/test-actions.cpp 2016-03-21 17:32:39 +0000
952+++ tests/test-actions.cpp 2016-04-28 14:56:46 +0000
953@@ -175,25 +175,25 @@
954 TEST_F(ActionsFixture, DesktopOpenAlarmApp)
955 {
956 test_action_with_no_args("desktop.open-alarm-app",
957- MockActions::DesktopOpenAlarmApp);
958+ MockActions::OpenAlarmApp);
959 }
960
961 TEST_F(ActionsFixture, DesktopOpenAppointment)
962 {
963 test_action_with_appt_arg("desktop.open-appointment",
964- MockActions::DesktopOpenAppt);
965+ MockActions::OpenAppt);
966 }
967
968 TEST_F(ActionsFixture, DesktopOpenCalendarApp)
969 {
970 test_action_with_time_arg("desktop.open-calendar-app",
971- MockActions::DesktopOpenCalendarApp);
972+ MockActions::OpenCalendarApp);
973 }
974
975 TEST_F(ActionsFixture, DesktopOpenSettingsApp)
976 {
977 test_action_with_no_args("desktop.open-settings-app",
978- MockActions::DesktopOpenSettingsApp);
979+ MockActions::OpenSettingsApp);
980 }
981
982 /***
983@@ -203,25 +203,25 @@
984 TEST_F(ActionsFixture, PhoneOpenAlarmApp)
985 {
986 test_action_with_no_args("phone.open-alarm-app",
987- MockActions::PhoneOpenAlarmApp);
988+ MockActions::OpenAlarmApp);
989 }
990
991 TEST_F(ActionsFixture, PhoneOpenAppointment)
992 {
993 test_action_with_appt_arg("phone.open-appointment",
994- MockActions::PhoneOpenAppt);
995+ MockActions::OpenAppt);
996 }
997
998 TEST_F(ActionsFixture, PhoneOpenCalendarApp)
999 {
1000 test_action_with_time_arg("phone.open-calendar-app",
1001- MockActions::PhoneOpenCalendarApp);
1002+ MockActions::OpenCalendarApp);
1003 }
1004
1005 TEST_F(ActionsFixture, PhoneOpenSettingsApp)
1006 {
1007 test_action_with_no_args("phone.open-settings-app",
1008- MockActions::PhoneOpenSettingsApp);
1009+ MockActions::OpenSettingsApp);
1010 }
1011
1012 /***
1013
1014=== modified file 'tests/test-live-actions.cpp'
1015--- tests/test-live-actions.cpp 2016-04-10 00:07:04 +0000
1016+++ tests/test-live-actions.cpp 2016-04-28 14:56:46 +0000
1017@@ -29,10 +29,16 @@
1018 public:
1019 std::string last_cmd;
1020 std::string last_url;
1021+
1022+
1023 explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {}
1024 ~MockLiveActions() {}
1025+ void set_desktop(LiveActions::Desktop desktop) { m_desktop = desktop; }
1026
1027 protected:
1028+ LiveActions::Desktop m_desktop;
1029+
1030+ LiveActions::Desktop get_desktop() override { return m_desktop; }
1031 void dispatch_url(const std::string& url) override { last_url = url; }
1032 void execute_command(const std::string& cmd) override { last_cmd = cmd; }
1033 };
1034@@ -112,31 +118,39 @@
1035
1036 TEST_F(TestLiveActionsFixture, DesktopOpenAlarmApp)
1037 {
1038- m_actions->desktop_open_alarm_app();
1039+ m_live_actions->set_desktop(LiveActions::UNITY7);
1040+
1041+ m_actions->open_alarm_app();
1042 const std::string expected = "evolution -c calendar";
1043 EXPECT_EQ(expected, m_live_actions->last_cmd);
1044 }
1045
1046 TEST_F(TestLiveActionsFixture, DesktopOpenAppointment)
1047 {
1048+ m_live_actions->set_desktop(LiveActions::UNITY7);
1049+
1050 Appointment a;
1051 a.uid = "some-uid";
1052 a.begin = DateTime::NowLocal();
1053- m_actions->desktop_open_appointment(a, a.begin);
1054+ m_actions->open_appointment(a, a.begin);
1055 const std::string expected_substr = "evolution \"calendar:///?startdate=";
1056 EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
1057 }
1058
1059 TEST_F(TestLiveActionsFixture, DesktopOpenCalendarApp)
1060 {
1061- m_actions->desktop_open_calendar_app(DateTime::NowLocal());
1062+ m_live_actions->set_desktop(LiveActions::UNITY7);
1063+
1064+ m_actions->open_calendar_app(DateTime::NowLocal());
1065 const std::string expected_substr = "evolution \"calendar:///?startdate=";
1066 EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
1067 }
1068
1069 TEST_F(TestLiveActionsFixture, DesktopOpenSettingsApp)
1070 {
1071- m_actions->desktop_open_settings_app();
1072+ m_live_actions->set_desktop(LiveActions::UNITY7);
1073+
1074+ m_actions->open_settings_app();
1075 const std::string expected_substr = "control-center";
1076 EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
1077 }
1078@@ -152,12 +166,16 @@
1079
1080 TEST_F(TestLiveActionsFixture, PhoneOpenAlarmApp)
1081 {
1082- m_actions->phone_open_alarm_app();
1083+ m_live_actions->set_desktop(LiveActions::UNITY8);
1084+
1085+ m_actions->open_alarm_app();
1086 EXPECT_EQ(clock_app_url, m_live_actions->last_url);
1087 }
1088
1089 TEST_F(TestLiveActionsFixture, PhoneOpenAppointment)
1090 {
1091+ m_live_actions->set_desktop(LiveActions::UNITY8);
1092+
1093 Appointment a;
1094
1095 a.uid = "event-uid";
1096@@ -165,19 +183,21 @@
1097 a.begin = DateTime::NowLocal();
1098 a.type = Appointment::EVENT;
1099 auto ocurrenceDate = DateTime::Local(2014, 1, 1, 0, 0, 0);
1100- m_actions->phone_open_appointment(a, ocurrenceDate);
1101+ m_actions->open_appointment(a, ocurrenceDate);
1102 const std::string appointment_app_url = ocurrenceDate.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
1103 EXPECT_EQ(appointment_app_url, m_live_actions->last_url);
1104
1105 a.type = Appointment::UBUNTU_ALARM;
1106- m_actions->phone_open_appointment(a, a.begin);
1107+ m_actions->open_appointment(a, a.begin);
1108 EXPECT_EQ(clock_app_url, m_live_actions->last_url);
1109 }
1110
1111 TEST_F(TestLiveActionsFixture, PhoneOpenCalendarApp)
1112 {
1113+ m_live_actions->set_desktop(LiveActions::UNITY8);
1114+
1115 auto now = DateTime::NowLocal();
1116- m_actions->phone_open_calendar_app(now);
1117+ m_actions->open_calendar_app(now);
1118 const std::string expected = now.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
1119 EXPECT_EQ(expected, m_live_actions->last_url);
1120 }
1121@@ -185,7 +205,9 @@
1122
1123 TEST_F(TestLiveActionsFixture, PhoneOpenSettingsApp)
1124 {
1125- m_actions->phone_open_settings_app();
1126+ m_live_actions->set_desktop(LiveActions::UNITY8);
1127+
1128+ m_actions->open_settings_app();
1129 const std::string expected = "settings:///system/time-date";
1130 EXPECT_EQ(expected, m_live_actions->last_url);
1131 }
1132
1133=== modified file 'tests/test-notification.cpp'
1134--- tests/test-notification.cpp 2016-02-11 04:30:46 +0000
1135+++ tests/test-notification.cpp 2016-04-28 14:56:46 +0000
1136@@ -63,7 +63,7 @@
1137 bool expected_notify_called;
1138 bool expected_vibrate_called;
1139 } test_appts[] = {
1140- { appt, "reminder", "Event", true, true },
1141+ { appt, "calendar-app", "Event", true, true },
1142 { ualarm, "alarm-clock", "Alarm", true, true }
1143 };
1144
1145
1146=== modified file 'tests/test-sound.cpp'
1147--- tests/test-sound.cpp 2016-02-10 20:49:01 +0000
1148+++ tests/test-sound.cpp 2016-04-28 14:56:46 +0000
1149@@ -85,7 +85,7 @@
1150
1151 // confirm that the icon passed to Notify was "alarm-clock"
1152 g_variant_get_child (params, 2, "&s", &str);
1153- ASSERT_STREQ("reminder", str);
1154+ ASSERT_STREQ("calendar-app", str);
1155
1156 // confirm that the hints passed to Notify included a timeout matching duration_minutes
1157 int32_t i32;

Subscribers

People subscribed via source and target branches