1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * Show/hide trigger in a card.
11 * showTimeSec: (string|undefined),
18 * ID of an individual (uncombined) notification.
19 * This ID comes directly from the server.
23 var ServerNotificationId
;
26 * Data to build a dismissal request for a card from a specific group.
29 * notificationId: ServerNotificationId,
36 * Urls that need to be opened when clicking a notification or its buttons.
39 * messageUrl: (string|undefined),
40 * buttonUrls: (Array<string>|undefined)
46 * ID of a combined notification.
47 * This is the ID used with chrome.notifications API.
51 var ChromeNotificationId
;
54 * Notification as sent by the server.
57 * notificationId: ServerNotificationId,
58 * chromeNotificationId: ChromeNotificationId,
60 * chromeNotificationOptions: Object,
61 * actionUrls: (ActionUrls|undefined),
63 * locationBased: (boolean|undefined),
65 * cardTypeId: (number|undefined)
68 var ReceivedNotification
;
71 * Received notification in a self-sufficient form that doesn't require group's
72 * timestamp to calculate show and hide times.
75 * receivedNotification: ReceivedNotification,
76 * showTime: (number|undefined),
80 var UncombinedNotification
;
83 * Card combined from potentially multiple groups.
85 * @typedef {Array<UncombinedNotification>}
90 * Data entry that we store for every Chrome notification.
91 * |timestamp| is the time when corresponding Chrome notification was created or
92 * updated last time by cardSet.update().
95 * actionUrls: (ActionUrls|undefined),
96 * cardTypeId: (number|undefined),
98 * combinedCard: CombinedCard
102 var NotificationDataEntry
;
105 * Names for tasks that can be created by the this file.
107 var UPDATE_CARD_TASK_NAME
= 'update-card';
110 * Builds an object to manage notification card set.
111 * @return {Object} Card set interface.
113 function buildCardSet() {
114 var alarmPrefix
= 'card-';
117 * Creates/updates/deletes a Chrome notification.
118 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
120 * @param {(ReceivedNotification|undefined)} receivedNotification Google Now
121 * card represented as a set of parameters for showing a Chrome
122 * notification, or null if the notification needs to be deleted.
123 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
124 * called when each card is shown.
126 function updateNotification(
127 chromeNotificationId
, receivedNotification
, onCardShown
) {
129 'cardManager.updateNotification ' + chromeNotificationId
+ ' ' +
130 JSON
.stringify(receivedNotification
));
132 if (!receivedNotification
) {
133 instrumented
.notifications
.clear(chromeNotificationId
, function() {});
137 // Try updating the notification.
138 instrumented
.notifications
.update(
139 chromeNotificationId
,
140 receivedNotification
.chromeNotificationOptions
,
141 function(wasUpdated
) {
143 // If the notification wasn't updated, it probably didn't exist.
146 'cardManager.updateNotification ' + chromeNotificationId
+
147 ' not updated, creating instead');
148 instrumented
.notifications
.create(
149 chromeNotificationId
,
150 receivedNotification
.chromeNotificationOptions
,
151 function(newChromeNotificationId
) {
152 if (!newChromeNotificationId
|| chrome
.runtime
.lastError
) {
153 var errorMessage
= chrome
.runtime
.lastError
&&
154 chrome
.runtime
.lastError
.message
;
155 console
.error('notifications.create: ID=' +
156 newChromeNotificationId
+ ', ERROR=' + errorMessage
);
160 if (onCardShown
!== undefined)
161 onCardShown(receivedNotification
);
168 * Iterates uncombined notifications in a combined card, determining for
169 * each whether it's visible at the specified moment.
170 * @param {CombinedCard} combinedCard The combined card in question.
171 * @param {number} timestamp Time for which to calculate visibility.
172 * @param {function(UncombinedNotification, boolean)} callback Function
173 * invoked for every uncombined notification in |combinedCard|.
174 * The boolean parameter indicates whether the uncombined notification is
175 * visible at |timestamp|.
177 function iterateUncombinedNotifications(combinedCard
, timestamp
, callback
) {
178 for (var i
= 0; i
!= combinedCard
.length
; ++i
) {
179 var uncombinedNotification
= combinedCard
[i
];
180 var shouldShow
= !uncombinedNotification
.showTime
||
181 uncombinedNotification
.showTime
<= timestamp
;
182 var shouldHide
= uncombinedNotification
.hideTime
<= timestamp
;
184 callback(uncombinedNotification
, shouldShow
&& !shouldHide
);
189 * Refreshes (shows/hides) the notification corresponding to the combined card
190 * based on the current time and show-hide intervals in the combined card.
191 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
193 * @param {CombinedCard} combinedCard Combined cards with
194 * |chromeNotificationId|.
195 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
196 * name to group information.
197 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
198 * called when each card is shown.
199 * @return {(NotificationDataEntry|undefined)} Notification data entry for
200 * this card. It's 'undefined' if the card's life is over.
203 chromeNotificationId
, combinedCard
, notificationGroups
, onCardShown
) {
204 console
.log('cardManager.update ' + JSON
.stringify(combinedCard
));
206 chrome
.alarms
.clear(alarmPrefix
+ chromeNotificationId
);
207 var now
= Date
.now();
208 /** @type {(UncombinedNotification|undefined)} */
209 var winningCard
= undefined;
210 // Next moment of time when winning notification selection algotithm can
211 // potentially return a different notification.
212 /** @type {?number} */
213 var nextEventTime
= null;
215 // Find a winning uncombined notification: a highest-priority notification
216 // that needs to be shown now.
217 iterateUncombinedNotifications(
220 function(uncombinedCard
, visible
) {
221 // If the uncombined notification is visible now and set the winning
222 // card to it if its priority is higher.
225 uncombinedCard
.receivedNotification
.chromeNotificationOptions
.
227 winningCard
.receivedNotification
.chromeNotificationOptions
.
229 winningCard
= uncombinedCard
;
233 // Next event time is the closest hide or show event.
234 if (uncombinedCard
.showTime
&& uncombinedCard
.showTime
> now
) {
235 if (!nextEventTime
|| nextEventTime
> uncombinedCard
.showTime
)
236 nextEventTime
= uncombinedCard
.showTime
;
238 if (uncombinedCard
.hideTime
> now
) {
239 if (!nextEventTime
|| nextEventTime
> uncombinedCard
.hideTime
)
240 nextEventTime
= uncombinedCard
.hideTime
;
244 // Show/hide the winning card.
246 chromeNotificationId
,
247 winningCard
&& winningCard
.receivedNotification
,
251 // If we expect more events, create an alarm for the next one.
252 chrome
.alarms
.create(
253 alarmPrefix
+ chromeNotificationId
, {when
: nextEventTime
});
255 // The trick with stringify/parse is to create a copy of action URLs,
256 // otherwise notifications data with 2 pointers to the same object won't
257 // be stored correctly to chrome.storage.
258 var winningActionUrls
= winningCard
&&
259 winningCard
.receivedNotification
.actionUrls
&&
260 JSON
.parse(JSON
.stringify(
261 winningCard
.receivedNotification
.actionUrls
));
262 var winningCardTypeId
= winningCard
&&
263 winningCard
.receivedNotification
.cardTypeId
;
265 actionUrls
: winningActionUrls
,
266 cardTypeId
: winningCardTypeId
,
268 combinedCard
: combinedCard
271 // If there are no more events, we are done with this card. Note that all
272 // received notifications have hideTime.
273 verify(!winningCard
, 'No events left, but card is shown.');
274 clearCardFromGroups(chromeNotificationId
, notificationGroups
);
280 * Removes dismissed part of a card and refreshes the card. Returns remaining
281 * dismissals for the combined card and updated notification data.
282 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
284 * @param {NotificationDataEntry} notificationData Stored notification entry
286 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
287 * name to group information.
289 * dismissals: Array<DismissalData>,
290 * notificationData: (NotificationDataEntry|undefined)
293 function onDismissal(
294 chromeNotificationId
, notificationData
, notificationGroups
) {
295 /** @type {Array<DismissalData>} */
297 /** @type {Array<UncombinedNotification>} */
298 var newCombinedCard
= [];
300 // Determine which parts of the combined card need to be dismissed or to be
301 // preserved. We dismiss parts that were visible at the moment when the card
303 iterateUncombinedNotifications(
304 notificationData
.combinedCard
,
305 notificationData
.timestamp
,
306 function(uncombinedCard
, visible
) {
309 notificationId
: uncombinedCard
.receivedNotification
.notificationId
,
310 parameters
: uncombinedCard
.receivedNotification
.dismissal
313 newCombinedCard
.push(uncombinedCard
);
318 dismissals
: dismissals
,
319 notificationData
: update(
320 chromeNotificationId
, newCombinedCard
, notificationGroups
)
325 * Removes card information from |notificationGroups|.
326 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
328 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
329 * name to group information.
331 function clearCardFromGroups(chromeNotificationId
, notificationGroups
) {
332 console
.log('cardManager.clearCardFromGroups ' + chromeNotificationId
);
333 for (var groupName
in notificationGroups
) {
334 var group
= notificationGroups
[groupName
];
335 for (var i
= 0; i
!= group
.cards
.length
; ++i
) {
336 if (group
.cards
[i
].chromeNotificationId
== chromeNotificationId
) {
337 group
.cards
.splice(i
, 1);
344 instrumented
.alarms
.onAlarm
.addListener(function(alarm
) {
345 console
.log('cardManager.onAlarm ' + JSON
.stringify(alarm
));
347 if (alarm
.name
.indexOf(alarmPrefix
) == 0) {
348 // Alarm to show the card.
349 tasks
.add(UPDATE_CARD_TASK_NAME
, function() {
350 /** @type {ChromeNotificationId} */
351 var chromeNotificationId
= alarm
.name
.substring(alarmPrefix
.length
);
352 fillFromChromeLocalStorage({
353 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
354 notificationsData
: {},
355 /** @type {Object<StoredNotificationGroup>} */
356 notificationGroups
: {}
357 }).then(function(items
) {
358 console
.log('cardManager.onAlarm.get ' + JSON
.stringify(items
));
361 (items
.notificationsData
[chromeNotificationId
] &&
362 items
.notificationsData
[chromeNotificationId
].combinedCard
) || [];
364 var cardShownCallback
= undefined;
365 if (localStorage
['explanatoryCardsShown'] <
366 EXPLANATORY_CARDS_LINK_THRESHOLD
) {
367 cardShownCallback
= countExplanatoryCard
;
370 items
.notificationsData
[chromeNotificationId
] =
372 chromeNotificationId
,
374 items
.notificationGroups
,
377 chrome
.storage
.local
.set(items
);
385 onDismissal
: onDismissal