| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * @fileoverview The event page for Google Now for Chrome implementation. | 8 * @fileoverview The event page for Google Now for Chrome implementation. |
| 9 * The Google Now event page gets Google Now cards from the server and shows | 9 * The Google Now event page gets Google Now cards from the server and shows |
| 10 * them as Chrome notifications. | 10 * them as Chrome notifications. |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 145 INITIAL_POLLING_PERIOD_SECONDS, | 145 INITIAL_POLLING_PERIOD_SECONDS, |
| 146 MAXIMUM_POLLING_PERIOD_SECONDS); | 146 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 147 var dismissalAttempts = buildAttemptManager( | 147 var dismissalAttempts = buildAttemptManager( |
| 148 'dismiss', | 148 'dismiss', |
| 149 retryPendingDismissals, | 149 retryPendingDismissals, |
| 150 INITIAL_RETRY_DISMISS_PERIOD_SECONDS, | 150 INITIAL_RETRY_DISMISS_PERIOD_SECONDS, |
| 151 MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS); | 151 MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS); |
| 152 var cardSet = buildCardSet(); | 152 var cardSet = buildCardSet(); |
| 153 | 153 |
| 154 /** | 154 /** |
| 155 * Diagnostic event identifier. | 155 * Google Now UMA event identifier. |
| 156 * @enum {number} | 156 * @enum {number} |
| 157 */ | 157 */ |
| 158 var DiagnosticEvent = { | 158 var GoogleNowEvent = { |
| 159 REQUEST_FOR_CARDS_TOTAL: 0, | 159 REQUEST_FOR_CARDS_TOTAL: 0, |
| 160 REQUEST_FOR_CARDS_SUCCESS: 1, | 160 REQUEST_FOR_CARDS_SUCCESS: 1, |
| 161 CARDS_PARSE_SUCCESS: 2, | 161 CARDS_PARSE_SUCCESS: 2, |
| 162 DISMISS_REQUEST_TOTAL: 3, | 162 DISMISS_REQUEST_TOTAL: 3, |
| 163 DISMISS_REQUEST_SUCCESS: 4, | 163 DISMISS_REQUEST_SUCCESS: 4, |
| 164 LOCATION_REQUEST: 5, | 164 LOCATION_REQUEST: 5, |
| 165 LOCATION_UPDATE: 6, | 165 LOCATION_UPDATE: 6, |
| 166 EXTENSION_START: 7, | 166 EXTENSION_START: 7, |
| 167 SHOW_WELCOME_TOAST: 8, | 167 SHOW_WELCOME_TOAST: 8, |
| 168 EVENTS_TOTAL: 9 // EVENTS_TOTAL is not an event; all new events need to be | 168 EVENTS_TOTAL: 9 // EVENTS_TOTAL is not an event; all new events need to be |
| 169 // added before it. | 169 // added before it. |
| 170 }; | 170 }; |
| 171 | 171 |
| 172 /** | 172 /** |
| 173 * Records a diagnostic event. | 173 * Records a Google Now Event. |
| 174 * @param {DiagnosticEvent} event Event identifier. | 174 * @param {GoogleNowEvent} event Event identifier. |
| 175 */ | 175 */ |
| 176 function recordEvent(event) { | 176 function recordEvent(event) { |
| 177 var metricDescription = { | 177 var metricDescription = { |
| 178 metricName: 'GoogleNow.Event', | 178 metricName: 'GoogleNow.Event', |
| 179 type: 'histogram-linear', | 179 type: 'histogram-linear', |
| 180 min: 1, | 180 min: 1, |
| 181 max: DiagnosticEvent.EVENTS_TOTAL, | 181 max: GoogleNowEvent.EVENTS_TOTAL, |
| 182 buckets: DiagnosticEvent.EVENTS_TOTAL + 1 | 182 buckets: GoogleNowEvent.EVENTS_TOTAL + 1 |
| 183 }; | 183 }; |
| 184 | 184 |
| 185 chrome.metricsPrivate.recordValue(metricDescription, event); | 185 chrome.metricsPrivate.recordValue(metricDescription, event); |
| 186 } | 186 } |
| 187 | 187 |
| 188 /** | 188 /** |
| 189 * Adds authorization behavior to the request. | 189 * Adds authorization behavior to the request. |
| 190 * @param {XMLHttpRequest} request Server request. | 190 * @param {XMLHttpRequest} request Server request. |
| 191 * @param {function(boolean)} callbackBoolean Completion callback with 'success' | 191 * @param {function(boolean)} callbackBoolean Completion callback with 'success' |
| 192 * parameter. | 192 * parameter. |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 296 // Delete notifications that didn't receive an update. | 296 // Delete notifications that didn't receive an update. |
| 297 for (var notificationId in notifications) { | 297 for (var notificationId in notifications) { |
| 298 console.log('parseAndShowNotificationCards-delete-check ' + | 298 console.log('parseAndShowNotificationCards-delete-check ' + |
| 299 notificationId); | 299 notificationId); |
| 300 if (!(notificationId in updatedNotifications)) { | 300 if (!(notificationId in updatedNotifications)) { |
| 301 console.log('parseAndShowNotificationCards-delete ' + notificationId); | 301 console.log('parseAndShowNotificationCards-delete ' + notificationId); |
| 302 cardSet.clear(notificationId); | 302 cardSet.clear(notificationId); |
| 303 } | 303 } |
| 304 } | 304 } |
| 305 | 305 |
| 306 recordEvent(DiagnosticEvent.CARDS_PARSE_SUCCESS); | 306 recordEvent(GoogleNowEvent.CARDS_PARSE_SUCCESS); |
| 307 | 307 |
| 308 // Create/update notifications and store their new properties. | 308 // Create/update notifications and store their new properties. |
| 309 var newNotificationsData = {}; | 309 var newNotificationsData = {}; |
| 310 for (var i = 0; i < cards.length; ++i) { | 310 for (var i = 0; i < cards.length; ++i) { |
| 311 var card = cards[i]; | 311 var card = cards[i]; |
| 312 if (!(card.notificationId in updatedRecentDismissals)) { | 312 if (!(card.notificationId in updatedRecentDismissals)) { |
| 313 var notificationData = items.notificationsData[card.notificationId]; | 313 var notificationData = items.notificationsData[card.notificationId]; |
| 314 var previousVersion = notifications[card.notificationId] && | 314 var previousVersion = notifications[card.notificationId] && |
| 315 notificationData && | 315 notificationData && |
| 316 notificationData.cardCreateInfo && | 316 notificationData.cardCreateInfo && |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 357 */ | 357 */ |
| 358 function requestNotificationCards(position, callback) { | 358 function requestNotificationCards(position, callback) { |
| 359 console.log('requestNotificationCards ' + JSON.stringify(position) + | 359 console.log('requestNotificationCards ' + JSON.stringify(position) + |
| 360 ' from ' + NOTIFICATION_CARDS_URL); | 360 ' from ' + NOTIFICATION_CARDS_URL); |
| 361 | 361 |
| 362 if (!NOTIFICATION_CARDS_URL) { | 362 if (!NOTIFICATION_CARDS_URL) { |
| 363 callback(); | 363 callback(); |
| 364 return; | 364 return; |
| 365 } | 365 } |
| 366 | 366 |
| 367 recordEvent(DiagnosticEvent.REQUEST_FOR_CARDS_TOTAL); | 367 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); |
| 368 | 368 |
| 369 // TODO(vadimt): Should we use 'q' as the parameter name? | 369 // TODO(vadimt): Should we use 'q' as the parameter name? |
| 370 var requestParameters = | 370 var requestParameters = |
| 371 'q=' + position.coords.latitude + | 371 'q=' + position.coords.latitude + |
| 372 ',' + position.coords.longitude + | 372 ',' + position.coords.longitude + |
| 373 ',' + position.coords.accuracy; | 373 ',' + position.coords.accuracy; |
| 374 | 374 |
| 375 var request = buildServerRequest('notifications', | 375 var request = buildServerRequest('notifications', |
| 376 'application/x-www-form-urlencoded'); | 376 'application/x-www-form-urlencoded'); |
| 377 | 377 |
| 378 request.onloadend = function(event) { | 378 request.onloadend = function(event) { |
| 379 console.log('requestNotificationCards-onloadend ' + request.status); | 379 console.log('requestNotificationCards-onloadend ' + request.status); |
| 380 if (request.status == HTTP_OK) { | 380 if (request.status == HTTP_OK) { |
| 381 recordEvent(DiagnosticEvent.REQUEST_FOR_CARDS_SUCCESS); | 381 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); |
| 382 parseAndShowNotificationCards(request.response, callback); | 382 parseAndShowNotificationCards(request.response, callback); |
| 383 } else { | 383 } else { |
| 384 callback(); | 384 callback(); |
| 385 } | 385 } |
| 386 }; | 386 }; |
| 387 | 387 |
| 388 setAuthorization(request, function(success) { | 388 setAuthorization(request, function(success) { |
| 389 if (success) { | 389 if (success) { |
| 390 tasks.debugSetStepName('requestNotificationCards-send-request'); | 390 tasks.debugSetStepName('requestNotificationCards-send-request'); |
| 391 request.send(requestParameters); | 391 request.send(requestParameters); |
| 392 } else { | 392 } else { |
| 393 callback(); | 393 callback(); |
| 394 } | 394 } |
| 395 }); | 395 }); |
| 396 } | 396 } |
| 397 | 397 |
| 398 /** | 398 /** |
| 399 * Starts getting location for a cards update. | 399 * Starts getting location for a cards update. |
| 400 */ | 400 */ |
| 401 function requestLocation() { | 401 function requestLocation() { |
| 402 console.log('requestLocation'); | 402 console.log('requestLocation'); |
| 403 recordEvent(DiagnosticEvent.LOCATION_REQUEST); | 403 recordEvent(GoogleNowEvent.LOCATION_REQUEST); |
| 404 // TODO(vadimt): Figure out location request options. | 404 // TODO(vadimt): Figure out location request options. |
| 405 chrome.location.watchLocation(LOCATION_WATCH_NAME, {}); | 405 chrome.location.watchLocation(LOCATION_WATCH_NAME, {}); |
| 406 } | 406 } |
| 407 | 407 |
| 408 /** | 408 /** |
| 409 * Stops getting the location. | 409 * Stops getting the location. |
| 410 */ | 410 */ |
| 411 function stopRequestLocation() { | 411 function stopRequestLocation() { |
| 412 console.log('stopRequestLocation'); | 412 console.log('stopRequestLocation'); |
| 413 chrome.location.clearWatch(LOCATION_WATCH_NAME); | 413 chrome.location.clearWatch(LOCATION_WATCH_NAME); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 454 console.log('requestDismissingCard ' + notificationId + ' from ' + | 454 console.log('requestDismissingCard ' + notificationId + ' from ' + |
| 455 NOTIFICATION_CARDS_URL); | 455 NOTIFICATION_CARDS_URL); |
| 456 | 456 |
| 457 var dismissalAge = Date.now() - dismissalTimeMs; | 457 var dismissalAge = Date.now() - dismissalTimeMs; |
| 458 | 458 |
| 459 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) { | 459 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) { |
| 460 callbackBoolean(true); | 460 callbackBoolean(true); |
| 461 return; | 461 return; |
| 462 } | 462 } |
| 463 | 463 |
| 464 recordEvent(DiagnosticEvent.DISMISS_REQUEST_TOTAL); | 464 recordEvent(GoogleNowEvent.DISMISS_REQUEST_TOTAL); |
| 465 var request = buildServerRequest('dismiss', 'application/json'); | 465 var request = buildServerRequest('dismiss', 'application/json'); |
| 466 request.onloadend = function(event) { | 466 request.onloadend = function(event) { |
| 467 console.log('requestDismissingCard-onloadend ' + request.status); | 467 console.log('requestDismissingCard-onloadend ' + request.status); |
| 468 if (request.status == HTTP_OK) | 468 if (request.status == HTTP_OK) |
| 469 recordEvent(DiagnosticEvent.DISMISS_REQUEST_SUCCESS); | 469 recordEvent(GoogleNowEvent.DISMISS_REQUEST_SUCCESS); |
| 470 | 470 |
| 471 // A dismissal doesn't require further retries if it was successful or | 471 // A dismissal doesn't require further retries if it was successful or |
| 472 // doesn't have a chance for successful completion. | 472 // doesn't have a chance for successful completion. |
| 473 var done = request.status == HTTP_OK || | 473 var done = request.status == HTTP_OK || |
| 474 request.status == HTTP_BAD_REQUEST || | 474 request.status == HTTP_BAD_REQUEST || |
| 475 request.status == HTTP_METHOD_NOT_ALLOWED; | 475 request.status == HTTP_METHOD_NOT_ALLOWED; |
| 476 callbackBoolean(done); | 476 callbackBoolean(done); |
| 477 }; | 477 }; |
| 478 | 478 |
| 479 setAuthorization(request, function(success) { | 479 setAuthorization(request, function(success) { |
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 677 | 677 |
| 678 updateCardsAttempts.stop(); | 678 updateCardsAttempts.stop(); |
| 679 | 679 |
| 680 removeAllCards(); | 680 removeAllCards(); |
| 681 } | 681 } |
| 682 | 682 |
| 683 /** | 683 /** |
| 684 * Initializes the event page on install or on browser startup. | 684 * Initializes the event page on install or on browser startup. |
| 685 */ | 685 */ |
| 686 function initialize() { | 686 function initialize() { |
| 687 recordEvent(DiagnosticEvent.EXTENSION_START); | 687 recordEvent(GoogleNowEvent.EXTENSION_START); |
| 688 | 688 |
| 689 // Alarms persist across chrome restarts. This is undesirable since it | 689 // Alarms persist across chrome restarts. This is undesirable since it |
| 690 // prevents us from starting up everything (alarms are a heuristic to | 690 // prevents us from starting up everything (alarms are a heuristic to |
| 691 // determine if we are already running). To mitigate this, we will | 691 // determine if we are already running). To mitigate this, we will |
| 692 // shut everything down on initialize before starting everything up. | 692 // shut everything down on initialize before starting everything up. |
| 693 stopPollingCards(); | 693 stopPollingCards(); |
| 694 onStateChange(); | 694 onStateChange(); |
| 695 } | 695 } |
| 696 | 696 |
| 697 /** | 697 /** |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 808 }); | 808 }); |
| 809 }); | 809 }); |
| 810 }); | 810 }); |
| 811 } | 811 } |
| 812 | 812 |
| 813 /** | 813 /** |
| 814 * Displays a toast to the user asking if they want to opt in to receiving | 814 * Displays a toast to the user asking if they want to opt in to receiving |
| 815 * Google Now cards. | 815 * Google Now cards. |
| 816 */ | 816 */ |
| 817 function showWelcomeToast() { | 817 function showWelcomeToast() { |
| 818 recordEvent(DiagnosticEvent.SHOW_WELCOME_TOAST); | 818 recordEvent(GoogleNowEvent.SHOW_WELCOME_TOAST); |
| 819 // TODO(zturner): Localize this once the component extension localization | 819 // TODO(zturner): Localize this once the component extension localization |
| 820 // api is complete. | 820 // api is complete. |
| 821 // TODO(zturner): Add icons. | 821 // TODO(zturner): Add icons. |
| 822 var buttons = [{title: 'Yes'}, {title: 'No'}]; | 822 var buttons = [{title: 'Yes'}, {title: 'No'}]; |
| 823 var options = { | 823 var options = { |
| 824 type: 'basic', | 824 type: 'basic', |
| 825 title: 'Enable Google Now Cards', | 825 title: 'Enable Google Now Cards', |
| 826 message: 'Would you like to be shown Google Now cards?', | 826 message: 'Would you like to be shown Google Now cards?', |
| 827 iconUrl: 'http://www.gstatic.com/googlenow/chrome/default.png', | 827 iconUrl: 'http://www.gstatic.com/googlenow/chrome/default.png', |
| 828 priority: 2, | 828 priority: 2, |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 878 return undefined; | 878 return undefined; |
| 879 | 879 |
| 880 return actionUrls.buttonUrls[buttonIndex]; | 880 return actionUrls.buttonUrls[buttonIndex]; |
| 881 }); | 881 }); |
| 882 } | 882 } |
| 883 }); | 883 }); |
| 884 | 884 |
| 885 chrome.notifications.onClosed.addListener(onNotificationClosed); | 885 chrome.notifications.onClosed.addListener(onNotificationClosed); |
| 886 | 886 |
| 887 chrome.location.onLocationUpdate.addListener(function(position) { | 887 chrome.location.onLocationUpdate.addListener(function(position) { |
| 888 recordEvent(DiagnosticEvent.LOCATION_UPDATE); | 888 recordEvent(GoogleNowEvent.LOCATION_UPDATE); |
| 889 updateNotificationsCards(position); | 889 updateNotificationsCards(position); |
| 890 }); | 890 }); |
| 891 | 891 |
| 892 chrome.omnibox.onInputEntered.addListener(function(text) { | 892 chrome.omnibox.onInputEntered.addListener(function(text) { |
| 893 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; | 893 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; |
| 894 initialize(); | 894 initialize(); |
| 895 }); | 895 }); |
| OLD | NEW |