blob: fb0a3ca88b5c1d760825ec5428689533f45138b4 [file] [log] [blame]
// 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.Bundle;
import android.os.Parcel;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.ipc.invalidation.ticl.android2.channel.GcmUpstreamSenderService;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.signin.OAuth2TokenService;
import org.chromium.components.sync.SyncConstants;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import java.io.IOException;
import java.util.UUID;
/**
* 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) {
final Bundle dataToSend = createDeepCopy(data);
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
doDeliverMessage(ContextUtils.getApplicationContext(), to, dataToSend);
}
});
}
// Incorrectly infers that this is called on a worker thread because of AsyncTask doInBackground
// overriding.
@SuppressWarnings("WrongThread")
@MainThread
private void doDeliverMessage(
final Context applicationContext, final String to, final Bundle data) {
ThreadUtils.assertOnUiThread();
ProcessInitializationHandler.getInstance().initializePreNative();
@Nullable
Account account = ChromeSigninController.get().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;
}
// Attempt to retrieve a token for the user.
OAuth2TokenService.getAccessToken(account, SyncConstants.CHROME_SYNC_OAUTH2_SCOPE,
new OAuth2TokenService.GetAccessTokenCallback() {
@Override
public void onGetTokenSuccess(final String token) {
new AsyncTask<Void>() {
@Override
protected Void doInBackground() {
sendUpstreamMessage(to, data, token, applicationContext);
return null;
}
}
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onGetTokenFailure(boolean isTransientError) {
GcmUma.recordGcmUpstreamHistogram(ContextUtils.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(ContextUtils.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;
}
}