// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.services.gcm; import android.accounts.Account; import android.annotation.SuppressLint; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcel; import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; import com.google.ipc.invalidation.ticl.android2.channel.GcmUpstreamSenderService; import org.chromium.chrome.browser.signin.OAuth2TokenService; import org.chromium.components.signin.AccountManagerHelper; import org.chromium.components.signin.ChromeSigninController; import org.chromium.components.sync.SyncConstants; import java.io.IOException; import java.util.UUID; import javax.annotation.Nullable; /** * Sends Upstream messages for Invalidations using GCM. */ public class InvalidationGcmUpstreamSender extends GcmUpstreamSenderService { private static final String TAG = "InvalidationGcmUpstream"; // GCM Payload Limit in bytes. private static final int GCM_PAYLOAD_LIMIT = 4000; @Override public void deliverMessage(final String to, final Bundle data) { @Nullable Account account = ChromeSigninController.get(this).getSignedInUser(); if (account == null) { // This should never happen, because this code should only be run if a user is // signed-in. Log.w(TAG, "No signed-in user; cannot send message to data center"); return; } final Bundle dataToSend = createDeepCopy(data); final Context applicationContext = getApplicationContext(); // Attempt to retrieve a token for the user. OAuth2TokenService.getOAuth2AccessToken(this, account, SyncConstants.CHROME_SYNC_OAUTH2_SCOPE, new AccountManagerHelper.GetAuthTokenCallback() { @Override public void tokenAvailable(final String token) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { sendUpstreamMessage(to, dataToSend, token, applicationContext); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @Override public void tokenUnavailable(boolean isTransientError) { GcmUma.recordGcmUpstreamHistogram( getApplicationContext(), GcmUma.UMA_UPSTREAM_TOKEN_REQUEST_FAILED); } }); } /* * This function runs on a thread from the AsyncTask.THREAD_POOL_EXECUTOR. */ private void sendUpstreamMessage(String to, Bundle data, String token, Context context) { // Add the OAuth2 token to the bundle. The token should have the prefix Bearer added to it. data.putString("Authorization", "Bearer " + token); if (!isMessageWithinLimit(data)) { GcmUma.recordGcmUpstreamHistogram(context, GcmUma.UMA_UPSTREAM_SIZE_LIMIT_EXCEEDED); return; } String msgId = UUID.randomUUID().toString(); try { GoogleCloudMessaging.getInstance(getApplicationContext()).send(to, msgId, 1, data); } catch (IOException | IllegalArgumentException exception) { Log.w(TAG, "Send message failed"); GcmUma.recordGcmUpstreamHistogram(context, GcmUma.UMA_UPSTREAM_SEND_FAILED); } } private boolean isMessageWithinLimit(Bundle data) { int size = 0; for (String key : data.keySet()) { size += key.length() + data.getString(key).length(); } if (size > GCM_PAYLOAD_LIMIT) { return false; } return true; } /* * Creates and returns a deep copy of the original Bundle. */ // TODO(crbug.com/635567): Fix this properly. @SuppressLint("ParcelClassLoader") private Bundle createDeepCopy(Bundle original) { Parcel temp = Parcel.obtain(); original.writeToParcel(temp, 0); temp.setDataPosition(0); Bundle copy = temp.readBundle(); temp.recycle(); return copy; } }