Commit 9e7d64f9 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

Convert GCM related service classes to split compat

This change converts GCM service classes, and adds new SplitCompat
services for GcmTaskService and GcmListenerService.

Review from Patchset 1 to get the right diffs on the changes in the Impl
classes.

Bug: 1126301
Change-Id: I2bdb1f8c1b8ccefd45bfb786a9190970db9d8f3c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2459207Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815004}
parent 59326568
......@@ -2068,8 +2068,11 @@ android_library("base_monochrome_module_java") {
android_library("base_module_java") {
sources = [
"java/src/com/google/ipc/invalidation/ticl/android2/channel/GcmRegistrationTaskService.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundService.java",
"java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatGcmListenerService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatGcmTaskService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatIntentService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatJobService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatRemoteViewsService.java",
......@@ -2088,6 +2091,9 @@ android_library("base_module_java") {
"java/src/org/chromium/chrome/browser/omaha/OmahaClient.java",
"java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java",
"java/src/org/chromium/chrome/browser/prerender/ChromePrerenderService.java",
"java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerService.java",
"java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundService.java",
"java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java",
"java/src/org/chromium/chrome/browser/tracing/TracingNotificationService.java",
]
deps = [
......
......@@ -15,7 +15,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ChromeActionModeHandler.java",
"java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java",
"java/src/org/chromium/chrome/browser/ChromeApplication.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundService.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundServiceImpl.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
"java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
"java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java",
......@@ -1344,11 +1344,10 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java",
"java/src/org/chromium/chrome/browser/services/AccountsChangedReceiver.java",
"java/src/org/chromium/chrome/browser/services/AndroidChildAccountHelper.java",
"java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerService.java",
"java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundService.java",
"java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerServiceImpl.java",
"java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundServiceImpl.java",
"java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java",
"java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java",
"java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java",
"java/src/org/chromium/chrome/browser/settings/MainSettings.java",
"java/src/org/chromium/chrome/browser/settings/SettingsActivity.java",
"java/src/org/chromium/chrome/browser/settings/SettingsLauncherImpl.java",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Copyright 2020 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;
import android.content.Context;
import org.chromium.chrome.browser.base.SplitCompatGcmTaskService;
import org.chromium.chrome.browser.base.SplitCompatUtils;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.GcmTaskService;
import com.google.android.gms.gcm.TaskParams;
import org.chromium.base.Log;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.background_sync.BackgroundSyncBackgroundTaskScheduler;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.ServiceManagerStartupUtils;
import org.chromium.chrome.browser.offlinepages.BackgroundScheduler;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* {@link ChromeBackgroundService} is scheduled through the {@link GcmNetworkManager} when the
* browser needs to be launched for scheduled tasks, or in response to changing network or power
* conditions.
*/
public class ChromeBackgroundService extends GcmTaskService {
private static final String TAG = "BackgroundService";
@Override
@VisibleForTesting
public int onRunTask(final TaskParams params) {
final String taskTag = params.getTag();
final Context context = this;
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
switch (taskTag) {
case BackgroundSyncBackgroundTaskScheduler.TASK_TAG:
// Background Sync tasks are now scheduled using BackgroundTaskScheduler.
// This should be rare, and we simply reschedule using BackgroundTaskScheduler.
rescheduleOneShotBackgroundSyncTasks();
break;
case OfflinePageUtils.TASK_TAG:
// Offline pages are migrating to BackgroundTaskScheduler, therefore getting
// a task through ChromeBackgroundService should cause a rescheduling using
// the new component.
rescheduleOfflinePages();
break;
// This is only for tests.
case ServiceManagerStartupUtils.TASK_TAG:
handleServicificationStartupTask(context, taskTag);
break;
default:
Log.i(TAG, "Unknown task tag " + taskTag);
break;
}
});
return GcmNetworkManager.RESULT_SUCCESS;
}
private void handleServicificationStartupTask(Context context, String tag) {
launchBrowser(context, tag);
}
@VisibleForTesting
protected void launchBrowser(Context context, String tag) {
Log.i(TAG, "Launching browser");
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
}
@VisibleForTesting
protected void rescheduleBackgroundSyncTasksOnUpgrade() {
rescheduleOneShotBackgroundSyncTasks();
}
/** Reschedules offline pages (using appropriate version of Background Task Scheduler). */
private void rescheduleOfflinePages() {
BackgroundScheduler.getInstance().reschedule();
}
private void rescheduleOneShotBackgroundSyncTasks() {
BackgroundSyncBackgroundTaskScheduler.getInstance().reschedule(
BackgroundSyncBackgroundTaskScheduler.BackgroundSyncTask
.ONE_SHOT_SYNC_CHROME_WAKE_UP);
}
@Override
public void onInitializeTasks() {
rescheduleBackgroundSyncTasksOnUpgrade();
/** See {@link ChromeBackgroundServiceImpl}. */
public class ChromeBackgroundService extends SplitCompatGcmTaskService {
public ChromeBackgroundService() {
super(SplitCompatUtils.getIdentifierName(
"org.chromium.chrome.browser.ChromeBackgroundServiceImpl"));
}
}
// Copyright 2016 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;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.TaskParams;
import org.chromium.base.Log;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.background_sync.BackgroundSyncBackgroundTaskScheduler;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.ServiceManagerStartupUtils;
import org.chromium.chrome.browser.offlinepages.BackgroundScheduler;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* {@link ChromeBackgroundService} is scheduled through the {@link GcmNetworkManager} when the
* browser needs to be launched for scheduled tasks, or in response to changing network or power
* conditions.
*/
public class ChromeBackgroundServiceImpl extends ChromeBackgroundService.Impl {
private static final String TAG = "BackgroundService";
@Override
@VisibleForTesting
public int onRunTask(final TaskParams params) {
final String taskTag = params.getTag();
final Context context = getService();
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
switch (taskTag) {
case BackgroundSyncBackgroundTaskScheduler.TASK_TAG:
// Background Sync tasks are now scheduled using BackgroundTaskScheduler.
// This should be rare, and we simply reschedule using BackgroundTaskScheduler.
rescheduleOneShotBackgroundSyncTasks();
break;
case OfflinePageUtils.TASK_TAG:
// Offline pages are migrating to BackgroundTaskScheduler, therefore getting
// a task through ChromeBackgroundService should cause a rescheduling using
// the new component.
rescheduleOfflinePages();
break;
// This is only for tests.
case ServiceManagerStartupUtils.TASK_TAG:
handleServicificationStartupTask(context, taskTag);
break;
default:
Log.i(TAG, "Unknown task tag " + taskTag);
break;
}
});
return GcmNetworkManager.RESULT_SUCCESS;
}
private void handleServicificationStartupTask(Context context, String tag) {
launchBrowser(context, tag);
}
@VisibleForTesting
protected void launchBrowser(Context context, String tag) {
Log.i(TAG, "Launching browser");
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
}
@VisibleForTesting
protected void rescheduleBackgroundSyncTasksOnUpgrade() {
rescheduleOneShotBackgroundSyncTasks();
}
/** Reschedules offline pages (using appropriate version of Background Task Scheduler). */
private void rescheduleOfflinePages() {
BackgroundScheduler.getInstance().reschedule();
}
private void rescheduleOneShotBackgroundSyncTasks() {
BackgroundSyncBackgroundTaskScheduler.getInstance().reschedule(
BackgroundSyncBackgroundTaskScheduler.BackgroundSyncTask
.ONE_SHOT_SYNC_CHROME_WAKE_UP);
}
@Override
public void onInitializeTasks() {
rescheduleBackgroundSyncTasksOnUpgrade();
}
}
// Copyright 2020 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.base;
import android.content.Context;
import android.os.Bundle;
import com.google.android.gms.gcm.GcmListenerService;
/**
* GcmListenerService base class which will call through to the given {@link Impl}. This class must
* be present in the base module, while the Impl can be in the chrome module.
*/
public class SplitCompatGcmListenerService extends GcmListenerService {
private String mServiceClassName;
private Impl mImpl;
public SplitCompatGcmListenerService(String serviceClassName) {
mServiceClassName = serviceClassName;
}
@Override
protected void attachBaseContext(Context context) {
context = SplitCompatUtils.createChromeContext(context);
mImpl = (Impl) SplitCompatUtils.newInstance(context, mServiceClassName);
mImpl.setService(this);
super.attachBaseContext(context);
}
@Override
public void onCreate() {
super.onCreate();
mImpl.onCreate();
}
@Override
public void onMessageReceived(String from, Bundle data) {
mImpl.onMessageReceived(from, data);
}
@Override
public void onMessageSent(String msgId) {
mImpl.onMessageSent(msgId);
}
@Override
public void onSendError(String msgId, String error) {
mImpl.onSendError(msgId, error);
}
@Override
public void onDeletedMessages() {
mImpl.onDeletedMessages();
}
/**
* Holds the implementation of service logic. Will be called by {@link
* SplitCompatGcmListenerService}.
*/
public abstract static class Impl {
private SplitCompatGcmListenerService mService;
protected final void setService(SplitCompatGcmListenerService service) {
mService = service;
}
protected final SplitCompatGcmListenerService getService() {
return mService;
}
public void onCreate() {}
public void onMessageReceived(String from, Bundle data) {}
public void onMessageSent(String msgId) {}
public void onSendError(String msgId, String error) {}
public void onDeletedMessages() {}
}
}
// Copyright 2020 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.base;
import android.content.Context;
import com.google.android.gms.gcm.GcmTaskService;
import com.google.android.gms.gcm.TaskParams;
/**
* GcmTaskService base class which will call through to the given {@link Impl}. This class must be
* present in the base module, while the Impl can be in the chrome module.
*/
public class SplitCompatGcmTaskService extends GcmTaskService {
private String mServiceClassName;
private Impl mImpl;
public SplitCompatGcmTaskService(String serviceClassName) {
mServiceClassName = serviceClassName;
}
@Override
protected void attachBaseContext(Context context) {
context = SplitCompatUtils.createChromeContext(context);
mImpl = (Impl) SplitCompatUtils.newInstance(context, mServiceClassName);
mImpl.setService(this);
super.attachBaseContext(context);
}
@Override
public int onRunTask(TaskParams params) {
return mImpl.onRunTask(params);
}
@Override
public void onInitializeTasks() {
mImpl.onInitializeTasks();
}
/**
* Holds the implementation of service logic. Will be called by {@link
* SplitCompatGcmTaskService}.
*/
public abstract static class Impl {
private SplitCompatGcmTaskService mService;
protected final void setService(SplitCompatGcmTaskService service) {
mService = service;
}
protected final GcmTaskService getService() {
return mService;
}
public abstract int onRunTask(TaskParams params);
public void onInitializeTasks() {}
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Copyright 2020 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.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import org.chromium.chrome.browser.base.SplitCompatGcmListenerService;
import org.chromium.chrome.browser.base.SplitCompatUtils;
import com.google.android.gms.gcm.GcmListenerService;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.device.DeviceConditions;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.background_task_scheduler.TaskInfo;
import org.chromium.components.gcm_driver.GCMDriver;
import org.chromium.components.gcm_driver.GCMMessage;
import org.chromium.components.gcm_driver.InstanceIDFlags;
import org.chromium.components.gcm_driver.LazySubscriptionsManager;
import org.chromium.components.gcm_driver.SubscriptionFlagManager;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* Receives Downstream messages and status of upstream messages from GCM.
*/
public class ChromeGcmListenerService extends GcmListenerService {
private static final String TAG = "ChromeGcmListener";
@Override
public void onCreate() {
ProcessInitializationHandler.getInstance().initializePreNative();
super.onCreate();
}
@Override
public void onMessageReceived(final String from, final Bundle data) {
boolean hasCollapseKey = !TextUtils.isEmpty(data.getString("collapse_key"));
GcmUma.recordDataMessageReceived(ContextUtils.getApplicationContext(), hasCollapseKey);
// Dispatch the message to the GCM Driver for native features.
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
GCMMessage message = null;
try {
message = new GCMMessage(from, data);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Received an invalid GCM Message", e);
return;
}
scheduleOrDispatchMessageToDriver(message);
});
}
@Override
public void onMessageSent(String msgId) {
Log.d(TAG, "Message sent successfully. Message id: " + msgId);
GcmUma.recordGcmUpstreamHistogram(
ContextUtils.getApplicationContext(), GcmUma.UMA_UPSTREAM_SUCCESS);
}
@Override
public void onSendError(String msgId, String error) {
Log.w(TAG, "Error in sending message. Message id: " + msgId + " Error: " + error);
GcmUma.recordGcmUpstreamHistogram(
ContextUtils.getApplicationContext(), GcmUma.UMA_UPSTREAM_SEND_FAILED);
}
@Override
public void onDeletedMessages() {
// TODO(johnme): Ask GCM to include the subtype in this event.
Log.w(TAG, "Push messages were deleted, but we can't tell the Service Worker as we don't"
+ "know what subtype (app ID) it occurred for.");
GcmUma.recordDeletedMessages(ContextUtils.getApplicationContext());
}
/**
* Returns if we deliver the GCMMessage with a background service by calling
* Context#startService. This will only work if Android has put us in a whitelist to allow
* background services to be started.
*/
private static boolean maybeBypassScheduler(GCMMessage message) {
// Android only puts us on a whitelist for high priority messages.
if (message.getOriginalPriority() != GCMMessage.Priority.HIGH) {
return false;
}
final String subscriptionId = SubscriptionFlagManager.buildSubscriptionUniqueId(
message.getAppId(), message.getSenderId());
if (!SubscriptionFlagManager.hasFlags(subscriptionId, InstanceIDFlags.BYPASS_SCHEDULER)) {
return false;
}
try {
Context context = ContextUtils.getApplicationContext();
Intent intent = new Intent(context, GCMBackgroundService.class);
intent.putExtras(message.toBundle());
context.startService(intent);
return true;
} catch (IllegalStateException e) {
// Failed to start service, maybe we're not whitelisted? Fallback to using
// BackgroundTaskScheduler to start Chrome.
// TODO(knollr): Add metrics for this.
Log.e(TAG, "Could not start background service", e);
return false;
}
}
/**
* Returns if the |message| is sent from a lazy subscription and we persist it to be delivered
* the next time Chrome is launched into foreground.
*/
private static boolean maybePersistLazyMessage(GCMMessage message) {
if (isFullBrowserLoaded()) {
return false;
}
final String subscriptionId = LazySubscriptionsManager.buildSubscriptionUniqueId(
message.getAppId(), message.getSenderId());
boolean isSubscriptionLazy = LazySubscriptionsManager.isSubscriptionLazy(subscriptionId);
boolean isHighPriority = message.getOriginalPriority() == GCMMessage.Priority.HIGH;
// TODO(crbug.com/945402): Add metrics for the new high priority message logic.
boolean shouldPersistMessage = isSubscriptionLazy && !isHighPriority;
if (shouldPersistMessage) {
LazySubscriptionsManager.persistMessage(subscriptionId, message);
}
return shouldPersistMessage;
}
/**
* Schedules a background task via Job Scheduler to deliver the |message|. Delivery might get
* delayed by Android if the device is currently in doze mode.
*/
private static void scheduleBackgroundTask(GCMMessage message) {
// TODO(peter): Add UMA for measuring latency introduced by the BackgroundTaskScheduler.
TaskInfo backgroundTask =
TaskInfo.createOneOffTask(TaskIds.GCM_BACKGROUND_TASK_JOB_ID, 0 /* immediately */)
.setExtras(message.toBundle())
.build();
BackgroundTaskSchedulerFactory.getScheduler().schedule(
ContextUtils.getApplicationContext(), backgroundTask);
}
private static void recordWebPushMetrics(GCMMessage message) {
Context context = ContextUtils.getApplicationContext();
boolean inIdleMode = DeviceConditions.isCurrentlyInIdleMode(context);
boolean isHighPriority = message.getOriginalPriority() == GCMMessage.Priority.HIGH;
@GcmUma.WebPushDeviceState
int state;
if (inIdleMode) {
state = isHighPriority ? GcmUma.WebPushDeviceState.IDLE_HIGH_PRIORITY
: GcmUma.WebPushDeviceState.IDLE_NOT_HIGH_PRIORITY;
} else {
state = isHighPriority ? GcmUma.WebPushDeviceState.NOT_IDLE_HIGH_PRIORITY
: GcmUma.WebPushDeviceState.NOT_IDLE_NOT_HIGH_PRIORITY;
}
GcmUma.recordWebPushReceivedDeviceState(state);
}
/**
* If Chrome is backgrounded, messages coming from lazy subscriptions are
* persisted on disk and replayed next time Chrome is forgrounded. If Chrome is forgrounded or
* if the message isn't coming from a lazy subscription, this method either schedules |message|
* to be dispatched through the Job Scheduler, which we use on Android N and beyond, or
* immediately dispatches the message on other versions of Android. Some subscriptions bypass
* the Job Scheduler and use Context#startService instead if the |message| has a high priority.
* Must be called on the UI thread both for the BackgroundTaskScheduler and for dispatching the
* |message| to the GCMDriver.
*/
static void scheduleOrDispatchMessageToDriver(GCMMessage message) {
ThreadUtils.assertOnUiThread();
// GCMMessage#getAppId never returns null.
if (message.getAppId().startsWith("wp:")) {
recordWebPushMetrics(message);
}
// Check if we should only persist the message for now.
if (maybePersistLazyMessage(message)) {
return;
}
// Dispatch message immediately on pre N versions of Android.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
dispatchMessageToDriver(message);
return;
}
// Check if we should bypass the scheduler for high priority messages.
if (!maybeBypassScheduler(message)) {
scheduleBackgroundTask(message);
}
}
/**
* To be called when a GCM message is ready to be dispatched. Will initialise the native code
* of the browser process, and forward the message to the GCM Driver. Must be called on the UI
* thread.
*/
static void dispatchMessageToDriver(GCMMessage message) {
ThreadUtils.assertOnUiThread();
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
GCMDriver.dispatchMessage(message);
}
private static boolean isFullBrowserLoaded() {
return ChromeBrowserInitializer.getInstance().isFullBrowserInitialized();
/** See {@link ChromeGcmListenerServiceImpl}. */
public class ChromeGcmListenerService extends SplitCompatGcmListenerService {
public ChromeGcmListenerService() {
super(SplitCompatUtils.getIdentifierName(
"org.chromium.chrome.browser.services.gcm.ChromeGcmListenerServiceImpl"));
}
}
// 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.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.device.DeviceConditions;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.background_task_scheduler.TaskInfo;
import org.chromium.components.gcm_driver.GCMDriver;
import org.chromium.components.gcm_driver.GCMMessage;
import org.chromium.components.gcm_driver.InstanceIDFlags;
import org.chromium.components.gcm_driver.LazySubscriptionsManager;
import org.chromium.components.gcm_driver.SubscriptionFlagManager;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* Receives Downstream messages and status of upstream messages from GCM.
*/
public class ChromeGcmListenerServiceImpl extends ChromeGcmListenerService.Impl {
private static final String TAG = "ChromeGcmListener";
@Override
public void onCreate() {
ProcessInitializationHandler.getInstance().initializePreNative();
super.onCreate();
}
@Override
public void onMessageReceived(final String from, final Bundle data) {
boolean hasCollapseKey = !TextUtils.isEmpty(data.getString("collapse_key"));
GcmUma.recordDataMessageReceived(ContextUtils.getApplicationContext(), hasCollapseKey);
// Dispatch the message to the GCM Driver for native features.
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
GCMMessage message = null;
try {
message = new GCMMessage(from, data);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Received an invalid GCM Message", e);
return;
}
scheduleOrDispatchMessageToDriver(message);
});
}
@Override
public void onMessageSent(String msgId) {
Log.d(TAG, "Message sent successfully. Message id: " + msgId);
GcmUma.recordGcmUpstreamHistogram(
ContextUtils.getApplicationContext(), GcmUma.UMA_UPSTREAM_SUCCESS);
}
@Override
public void onSendError(String msgId, String error) {
Log.w(TAG, "Error in sending message. Message id: " + msgId + " Error: " + error);
GcmUma.recordGcmUpstreamHistogram(
ContextUtils.getApplicationContext(), GcmUma.UMA_UPSTREAM_SEND_FAILED);
}
@Override
public void onDeletedMessages() {
// TODO(johnme): Ask GCM to include the subtype in this event.
Log.w(TAG,
"Push messages were deleted, but we can't tell the Service Worker as we don't"
+ "know what subtype (app ID) it occurred for.");
GcmUma.recordDeletedMessages(ContextUtils.getApplicationContext());
}
/**
* Returns if we deliver the GCMMessage with a background service by calling
* Context#startService. This will only work if Android has put us in a whitelist to allow
* background services to be started.
*/
private static boolean maybeBypassScheduler(GCMMessage message) {
// Android only puts us on a whitelist for high priority messages.
if (message.getOriginalPriority() != GCMMessage.Priority.HIGH) {
return false;
}
final String subscriptionId = SubscriptionFlagManager.buildSubscriptionUniqueId(
message.getAppId(), message.getSenderId());
if (!SubscriptionFlagManager.hasFlags(subscriptionId, InstanceIDFlags.BYPASS_SCHEDULER)) {
return false;
}
try {
Context context = ContextUtils.getApplicationContext();
Intent intent = new Intent(context, GCMBackgroundService.class);
intent.putExtras(message.toBundle());
context.startService(intent);
return true;
} catch (IllegalStateException e) {
// Failed to start service, maybe we're not whitelisted? Fallback to using
// BackgroundTaskScheduler to start Chrome.
// TODO(knollr): Add metrics for this.
Log.e(TAG, "Could not start background service", e);
return false;
}
}
/**
* Returns if the |message| is sent from a lazy subscription and we persist it to be delivered
* the next time Chrome is launched into foreground.
*/
private static boolean maybePersistLazyMessage(GCMMessage message) {
if (isFullBrowserLoaded()) {
return false;
}
final String subscriptionId = LazySubscriptionsManager.buildSubscriptionUniqueId(
message.getAppId(), message.getSenderId());
boolean isSubscriptionLazy = LazySubscriptionsManager.isSubscriptionLazy(subscriptionId);
boolean isHighPriority = message.getOriginalPriority() == GCMMessage.Priority.HIGH;
// TODO(crbug.com/945402): Add metrics for the new high priority message logic.
boolean shouldPersistMessage = isSubscriptionLazy && !isHighPriority;
if (shouldPersistMessage) {
LazySubscriptionsManager.persistMessage(subscriptionId, message);
}
return shouldPersistMessage;
}
/**
* Schedules a background task via Job Scheduler to deliver the |message|. Delivery might get
* delayed by Android if the device is currently in doze mode.
*/
private static void scheduleBackgroundTask(GCMMessage message) {
// TODO(peter): Add UMA for measuring latency introduced by the BackgroundTaskScheduler.
TaskInfo backgroundTask =
TaskInfo.createOneOffTask(TaskIds.GCM_BACKGROUND_TASK_JOB_ID, 0 /* immediately */)
.setExtras(message.toBundle())
.build();
BackgroundTaskSchedulerFactory.getScheduler().schedule(
ContextUtils.getApplicationContext(), backgroundTask);
}
private static void recordWebPushMetrics(GCMMessage message) {
Context context = ContextUtils.getApplicationContext();
boolean inIdleMode = DeviceConditions.isCurrentlyInIdleMode(context);
boolean isHighPriority = message.getOriginalPriority() == GCMMessage.Priority.HIGH;
@GcmUma.WebPushDeviceState
int state;
if (inIdleMode) {
state = isHighPriority ? GcmUma.WebPushDeviceState.IDLE_HIGH_PRIORITY
: GcmUma.WebPushDeviceState.IDLE_NOT_HIGH_PRIORITY;
} else {
state = isHighPriority ? GcmUma.WebPushDeviceState.NOT_IDLE_HIGH_PRIORITY
: GcmUma.WebPushDeviceState.NOT_IDLE_NOT_HIGH_PRIORITY;
}
GcmUma.recordWebPushReceivedDeviceState(state);
}
/**
* If Chrome is backgrounded, messages coming from lazy subscriptions are
* persisted on disk and replayed next time Chrome is forgrounded. If Chrome is forgrounded or
* if the message isn't coming from a lazy subscription, this method either schedules |message|
* to be dispatched through the Job Scheduler, which we use on Android N and beyond, or
* immediately dispatches the message on other versions of Android. Some subscriptions bypass
* the Job Scheduler and use Context#startService instead if the |message| has a high priority.
* Must be called on the UI thread both for the BackgroundTaskScheduler and for dispatching the
* |message| to the GCMDriver.
*/
static void scheduleOrDispatchMessageToDriver(GCMMessage message) {
ThreadUtils.assertOnUiThread();
// GCMMessage#getAppId never returns null.
if (message.getAppId().startsWith("wp:")) {
recordWebPushMetrics(message);
}
// Check if we should only persist the message for now.
if (maybePersistLazyMessage(message)) {
return;
}
// Dispatch message immediately on pre N versions of Android.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
dispatchMessageToDriver(message);
return;
}
// Check if we should bypass the scheduler for high priority messages.
if (!maybeBypassScheduler(message)) {
scheduleBackgroundTask(message);
}
}
/**
* To be called when a GCM message is ready to be dispatched. Will initialise the native code
* of the browser process, and forward the message to the GCM Driver. Must be called on the UI
* thread.
*/
static void dispatchMessageToDriver(GCMMessage message) {
ThreadUtils.assertOnUiThread();
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
GCMDriver.dispatchMessage(message);
}
private static boolean isFullBrowserLoaded() {
return ChromeBrowserInitializer.getInstance().isFullBrowserInitialized();
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Copyright 2020 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.annotation.TargetApi;
import android.app.IntentService;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import org.chromium.chrome.browser.base.SplitCompatIntentService;
import org.chromium.chrome.browser.base.SplitCompatUtils;
import org.chromium.base.Log;
import org.chromium.base.task.PostTask;
import org.chromium.components.gcm_driver.GCMMessage;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* Service that dispatches a GCM message in the background. Launched from ChromeGcmListenerService
* if we received a high priority push message, as that should allow us to start a background
* service even if Chrome is not running.
*/
@TargetApi(Build.VERSION_CODES.N)
public class GCMBackgroundService extends IntentService {
/** See {@link GCMBackgroundServiceImpl}. */
public class GCMBackgroundService extends SplitCompatIntentService {
private static final String TAG = "GCMBackgroundService";
public GCMBackgroundService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GCMMessage message = GCMMessage.createFromBundle(extras);
if (message == null) {
Log.e(TAG, "The received bundle containing message data could not be validated.");
return;
}
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT,
() -> ChromeGcmListenerService.dispatchMessageToDriver(message));
super(SplitCompatUtils.getIdentifierName(
"org.chromium.chrome.browser.services.gcm.GCMBackgroundServiceImpl"),
TAG);
}
}
// Copyright 2019 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.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import org.chromium.base.Log;
import org.chromium.base.task.PostTask;
import org.chromium.components.gcm_driver.GCMMessage;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* Service that dispatches a GCM message in the background. Launched from ChromeGcmListenerService
* if we received a high priority push message, as that should allow us to start a background
* service even if Chrome is not running.
*/
@TargetApi(Build.VERSION_CODES.N)
public class GCMBackgroundServiceImpl extends GCMBackgroundService.Impl {
private static final String TAG = "GCMBackgroundService";
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GCMMessage message = GCMMessage.createFromBundle(extras);
if (message == null) {
Log.e(TAG, "The received bundle containing message data could not be validated.");
return;
}
PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT,
() -> ChromeGcmListenerServiceImpl.dispatchMessageToDriver(message));
}
}
......@@ -42,7 +42,7 @@ public class GCMBackgroundTask implements BackgroundTask {
return false;
}
ChromeGcmListenerService.dispatchMessageToDriver(message);
ChromeGcmListenerServiceImpl.dispatchMessageToDriver(message);
return false;
}
......
......@@ -39,7 +39,7 @@ import org.chromium.content_public.browser.UiThreadTaskTraits;
public class ChromeBackgroundServiceTest {
private MockTaskService mTaskService;
class MockTaskService extends ChromeBackgroundService {
class MockTaskService extends ChromeBackgroundServiceImpl {
private boolean mDidLaunchBrowser;
private boolean mDidCallOnPersistentSchedulerWakeUp;
private boolean mDidCallOnBrowserUpgraded;
......
......@@ -20,7 +20,7 @@ import org.chromium.content_public.browser.test.util.TestThreadUtils;
/**
* Class for launching the service manager only mode for tests.
*/
public class ServicificationBackgroundService extends ChromeBackgroundService {
public class ServicificationBackgroundService extends ChromeBackgroundServiceImpl {
private boolean mLaunchBrowserCalled;
private boolean mNativeLoaded;
private boolean mSupportsServiceManagerOnly;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment