Index: chrome/browser/resources/google_now/background.js |
diff --git a/chrome/browser/resources/google_now/background.js b/chrome/browser/resources/google_now/background.js |
index 087b2ac97912b99ca456bfc1bb2d45d74e1c8676..e24f70b499b482ea73d0a51204315cb4d34bcb2f 100644 |
--- a/chrome/browser/resources/google_now/background.js |
+++ b/chrome/browser/resources/google_now/background.js |
@@ -95,6 +95,17 @@ var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week |
var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome'; |
/** |
+ * GCM registration URL. |
+ */ |
+var GCM_REGISTRATION_URL = |
+ 'https://android.googleapis.com/gcm/googlenotification'; |
+ |
+/** |
+ * DevConsole project ID for GCM API use. |
+ */ |
+var GCM_PROJECT_ID = '437902709571'; |
+ |
+/** |
* Number of cards that need an explanatory link. |
*/ |
var EXPLANATORY_CARDS_LINK_THRESHOLD = 4; |
@@ -189,6 +200,8 @@ function areTasksConflicting(newTaskName, scheduledTaskName) { |
var tasks = buildTaskManager(areTasksConflicting); |
// Add error processing to API calls. |
+wrapper.instrumentChromeApiFunction('gcm.onMessage.addListener', 0); |
+wrapper.instrumentChromeApiFunction('gcm.register', 1); |
wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1); |
wrapper.instrumentChromeApiFunction('notifications.clear', 1); |
wrapper.instrumentChromeApiFunction('notifications.create', 2); |
@@ -204,10 +217,9 @@ wrapper.instrumentChromeApiFunction( |
wrapper.instrumentChromeApiFunction( |
'notifications.onShowSettings.addListener', 0); |
wrapper.instrumentChromeApiFunction('permissions.contains', 1); |
-wrapper.instrumentChromeApiFunction('pushMessaging.onMessage.addListener', 0); |
-wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); |
wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0); |
wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0); |
+wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); |
wrapper.instrumentChromeApiFunction('tabs.create', 1); |
var updateCardsAttempts = buildAttemptManager( |
@@ -1015,6 +1027,7 @@ function stopPollingCards() { |
*/ |
function initialize() { |
recordEvent(GoogleNowEvent.EXTENSION_START); |
+ registerForGcm(); |
onStateChange(); |
} |
@@ -1204,6 +1217,100 @@ function isGoogleNowEnabled() { |
} |
/** |
+ * Ensures the extension is ready to listen for GCM messages. |
+ */ |
+function registerForGcm() { |
+ // We don't need to use the key yet, just ensure the channel is set up. |
+ getGcmNotificationKey(); |
+} |
+ |
+/** |
+ * Returns a Promise resolving to either a cached or new GCM notification key. |
+ * Rejects if registration fails. |
+ * @return {Promise} A Promise that resolves to a potentially-cached GCM key. |
+ */ |
+function getGcmNotificationKey() { |
+ return fillFromChromeLocalStorage({gcmNotificationKey: undefined}) |
+ .then(function(items) { |
+ if (items.gcmNotificationKey) { |
+ console.log('Reused gcm key from storage.'); |
+ return Promise.resolve(items.gcmNotificationKey); |
+ } |
+ return requestNewGcmNotificationKey(); |
+ }); |
+} |
+ |
+/** |
+ * Returns a promise resolving to a GCM Notificaiton Key. May call |
+ * chrome.gcm.register() first if required. Rejects on registration failure. |
+ * @return {Promise} A Promise that resolves to a fresh GCM Notification key. |
+ */ |
+function requestNewGcmNotificationKey() { |
+ return getGcmRegistrationId().then(function(gcmId) { |
+ authenticationManager.getAuthToken().then(function(token) { |
+ authenticationManager.getLogin().then(function(username) { |
+ return new Promise(function(resolve, reject) { |
+ var xhr = new XMLHttpRequest(); |
+ xhr.responseType = 'application/json'; |
+ xhr.open('POST', GCM_REGISTRATION_URL, true); |
+ xhr.setRequestHeader('Content-Type', 'application/json'); |
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token); |
+ xhr.setRequestHeader('project_id', GCM_PROJECT_ID); |
+ var payload = { |
+ 'operation': 'add', |
+ 'notification_key_name': username, |
+ 'registration_ids': [gcmId] |
+ }; |
+ xhr.onloadend = function() { |
+ if (xhr.status != 200) { |
+ reject(); |
+ } |
+ var obj = JSON.parse(xhr.responseText); |
+ var key = obj && obj.notification_key; |
+ if (!key) { |
+ reject(); |
+ } |
+ console.log('gcm notification key POST: ' + key); |
+ chrome.storage.local.set({gcmNotificationKey: key}); |
+ resolve(key); |
+ }; |
+ xhr.send(JSON.stringify(payload)); |
+ }); |
+ }); |
+ }).catch(function() { |
+ // Couldn't obtain a GCM ID. Ignore and fallback to polling. |
+ }); |
+ }); |
+} |
+ |
+/** |
+ * Returns a promise resolving to either a cached or new GCM registration ID. |
+ * Rejects if registration fails. |
+ * @return {Promise} A Promise that resolves to a GCM registration ID. |
+ */ |
+function getGcmRegistrationId() { |
+ return fillFromChromeLocalStorage({gcmRegistrationId: undefined}) |
+ .then(function(items) { |
+ if (items.gcmRegistrationId) { |
+ console.log('Reused gcm registration id from storage.'); |
+ return Promise.resolve(items.gcmRegistrationId); |
+ } |
+ |
+ return new Promise(function(resolve, reject) { |
+ instrumented.gcm.register([GCM_PROJECT_ID], function(registrationId) { |
+ console.log('gcm.register(): ' + registrationId); |
+ if (registrationId) { |
+ chrome.storage.local.set({gcmRegistrationId: registrationId}); |
+ resolve(registrationId); |
+ } else { |
+ reject(); |
+ } |
+ }); |
+ }); |
+ }); |
+} |
+ |
+/** |
* Polls the optin state. |
* Sometimes we get the response to the opted in result too soon during |
* push messaging. We'll recheck the optin state a few times before giving up. |
@@ -1334,13 +1441,15 @@ instrumented.storage.onChanged.addListener(function(changes, areaName) { |
} |
}); |
-instrumented.pushMessaging.onMessage.addListener(function(message) { |
- // message.payload will be '' when the extension first starts. |
- // Each time after signing in, we'll get latest payload for all channels. |
- // So, we need to poll the server only when the payload is non-empty and has |
- // changed. |
- console.log('pushMessaging.onMessage ' + JSON.stringify(message)); |
- if (message.payload.indexOf('REQUEST_CARDS') == 0) { |
+instrumented.gcm.onMessage.addListener(function(message) { |
+ console.log('gcm.onMessage ' + JSON.stringify(message)); |
+ if (!message || !message.data) { |
+ return; |
+ } |
+ |
+ var payload = message.data.payload; |
+ var tag = message.data.tag; |
+ if (payload.indexOf('REQUEST_CARDS') == 0) { |
tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() { |
// Accept promise rejection on failure since it's safer to do nothing, |
// preventing polling the server when the payload really didn't change. |
@@ -1349,11 +1458,10 @@ instrumented.pushMessaging.onMessage.addListener(function(message) { |
/** @type {Object<string, StoredNotificationGroup>} */ |
notificationGroups: {} |
}, PromiseRejection.ALLOW).then(function(items) { |
- if (items.lastPollNowPayloads[message.subchannelId] != |
- message.payload) { |
- items.lastPollNowPayloads[message.subchannelId] = message.payload; |
+ if (items.lastPollNowPayloads[tag] != payload) { |
+ items.lastPollNowPayloads[tag] = payload; |
- items.notificationGroups['PUSH' + message.subchannelId] = { |
+ items.notificationGroups['PUSH' + tag] = { |
cards: [], |
nextPollTime: Date.now() |
}; |