initialize() {
- return Task.forResult(null);
- }
-
- @Override
- public void handlePush(Intent intent) {}
- }
-}
diff --git a/Parse/src/main/java/com/parse/PushRouter.java b/Parse/src/main/java/com/parse/PushRouter.java
index 3630e8918..2d51fdb87 100644
--- a/Parse/src/main/java/com/parse/PushRouter.java
+++ b/Parse/src/main/java/com/parse/PushRouter.java
@@ -29,7 +29,7 @@
* registration id for a client (which can result in duplicate pushes while both the old and
* new registration id are still valid).
*/
-/** package */ class PushRouter {
+public class PushRouter {
private static final String TAG = "com.parse.ParsePushRouter";
private static final String LEGACY_STATE_LOCATION = "pushState";
private static final String STATE_LOCATION = "push";
diff --git a/Parse/src/main/java/com/parse/PushService.java b/Parse/src/main/java/com/parse/PushService.java
deleted file mode 100644
index f7de87a66..000000000
--- a/Parse/src/main/java/com/parse/PushService.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (c) 2015-present, Parse, LLC.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-package com.parse;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * A service to listen for push notifications. This operates in the same process as the parent
- * application.
- *
- * The {@code PushService} can listen to pushes from Google Cloud Messaging (GCM).
- * To configure the {@code PushService} for GCM, ensure these permission declarations are present in
- * your AndroidManifest.xml as children of the <manifest>
element:
- *
- *
- * <uses-permission android:name="android.permission.INTERNET" />
- * <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- * <uses-permission android:name="android.permission.VIBRATE" />
- * <uses-permission android:name="android.permission.WAKE_LOCK" />
- * <uses-permission android:name="android.permission.GET_ACCOUNTS" />
- * <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
- * <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE"
- * android:protectionLevel="signature" />
- * <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" />
- *
- *
- * Replace YOUR_PACKAGE_NAME in the declarations above with your application's package name. Also,
- * make sure that {@link GcmBroadcastReceiver}, {@link PushService} and
- * {@link ParsePushBroadcastReceiver} are declared as children of the
- * <application>
element:
- *
- *
- * <service android:name="com.parse.PushService" />
- * <receiver android:name="com.parse.GcmBroadcastReceiver"
- * android:permission="com.google.android.c2dm.permission.SEND">
- * <intent-filter>
- * <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- * <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
- * <category android:name="YOUR_PACKAGE_NAME" />
- * </intent-filter>
- * </receiver>
- * <receiver android:name="com.parse.ParsePushBroadcastReceiver" android:exported=false>
- * <intent-filter>
- * <action android:name="com.parse.push.intent.RECEIVE" />
- * <action android:name="com.parse.push.intent.OPEN" />
- * <action android:name="com.parse.push.intent.DELETE" />
- * </intent-filter>
- * </receiver>
- *
- *
- * Again, replace YOUR_PACKAGE_NAME with your application's package name.
- * If you want to customize the way your app generates Notifications for your pushes, you
- * can register a custom subclass of {@link ParsePushBroadcastReceiver}.
- *
- * Once push notifications are configured in the manifest, you can subscribe to a push channel by
- * calling:
- *
- *
- * ParsePush.subscribeInBackground("the_channel_name");
- *
- *
- * When the client receives a push message, a notification will appear in the system tray. When the
- * user taps the notification, it will broadcast the "com.parse.push.intent.OPEN" intent.
- * The {@link ParsePushBroadcastReceiver} listens to this intent to track an app open event and
- * launch the app's launcher activity. To customize this behavior override
- * {@link ParsePushBroadcastReceiver#onPushOpen(Context, Intent)}.
- *
- * Starting with Android O, this is replaced by {@link PushServiceApi26}.
- */
-public final class PushService extends Service {
- private static final String TAG = "com.parse.PushService";
-
- //region run and dispose
-
- private static final String WAKE_LOCK_EXTRA = "parseWakeLockId";
- private static final SparseArray wakeLocks = new SparseArray<>();
- private static int wakeLockId = 0;
-
- /*
- * Same as Context.startService, but acquires a wake lock before starting the service. The wake
- * lock must later be released by calling dispose().
- */
- static boolean run(Context context, Intent intent) {
- String reason = intent.toString();
- ParseWakeLock wl = ParseWakeLock.acquireNewWakeLock(context, PowerManager.PARTIAL_WAKE_LOCK, reason, 0);
-
- synchronized (wakeLocks) {
- intent.putExtra(WAKE_LOCK_EXTRA, wakeLockId);
- wakeLocks.append(wakeLockId, wl);
- wakeLockId++;
- }
-
- intent.setClass(context, PushService.class);
- ComponentName name = context.startService(intent);
- if (name == null) {
- PLog.e(TAG, "Could not start the service. Make sure that the XML tag "
- + " is in your "
- + "AndroidManifest.xml as a child of the element.");
- dispose(intent);
- return false;
- }
- return true;
- }
-
- static void dispose(Intent intent) {
- if (intent != null && intent.hasExtra(WAKE_LOCK_EXTRA)) {
- int id = intent.getIntExtra(WAKE_LOCK_EXTRA, -1);
- ParseWakeLock wakeLock;
-
- synchronized (wakeLocks) {
- wakeLock = wakeLocks.get(id);
- wakeLocks.remove(id);
- }
-
- if (wakeLock == null) {
- PLog.e(TAG, "Got wake lock id of " + id + " in intent, but no such lock found in " +
- "global map. Was disposePushService called twice for the same intent?");
- } else {
- wakeLock.release();
- }
- }
- }
-
- //region ServiceLifecycleCallbacks used for testing
-
- private static List serviceLifecycleCallbacks = null;
-
- /* package */ interface ServiceLifecycleCallbacks {
- void onServiceCreated(Service service);
- void onServiceDestroyed(Service service);
- }
-
- /* package */ static void registerServiceLifecycleCallbacks(ServiceLifecycleCallbacks callbacks) {
- synchronized (PushService.class) {
- if (serviceLifecycleCallbacks == null) {
- serviceLifecycleCallbacks = new ArrayList<>();
- }
- serviceLifecycleCallbacks.add(callbacks);
- }
- }
-
- /* package */ static void unregisterServiceLifecycleCallbacks(ServiceLifecycleCallbacks callbacks) {
- synchronized (PushService.class) {
- serviceLifecycleCallbacks.remove(callbacks);
- }
- }
-
- private static void dispatchOnServiceCreated(Service service) {
- if (serviceLifecycleCallbacks != null) {
- for (ServiceLifecycleCallbacks callback : serviceLifecycleCallbacks) {
- callback.onServiceCreated(service);
- }
- }
- }
-
- private static void dispatchOnServiceDestroyed(Service service) {
- if (serviceLifecycleCallbacks != null) {
- for (ServiceLifecycleCallbacks callback : serviceLifecycleCallbacks) {
- callback.onServiceDestroyed(service);
- }
- }
- }
-
- //endregion
-
- // We delegate the intent to a PushHandler running in a streamlined executor.
- private ExecutorService executor;
- private PushHandler handler;
-
- /**
- * Client code should not construct a PushService directly.
- */
- public PushService() {
- super();
- }
-
- // For tests
- void setPushHandler(PushHandler handler) {
- this.handler = handler;
- }
-
- /**
- * Called at startup at the moment of parsing the manifest, to see
- * if it was correctly set-up.
- */
- static boolean isSupported() {
- return ManifestInfo.getServiceInfo(PushService.class) != null;
- }
-
-
- /**
- * Client code should not call {@code onCreate} directly.
- */
- @Override
- public void onCreate() {
- super.onCreate();
- if (ParsePlugins.get() == null) {
- PLog.e(TAG, "The Parse push service cannot start because Parse.initialize "
- + "has not yet been called. If you call Parse.initialize from "
- + "an Activity's onCreate, that call should instead be in the "
- + "Application.onCreate. Be sure your Application class is registered "
- + "in your AndroidManifest.xml with the android:name property of your "
- + " tag.");
- stopSelf();
- return;
- }
-
- executor = Executors.newSingleThreadExecutor();
- handler = PushServiceUtils.createPushHandler();
- dispatchOnServiceCreated(this);
- }
-
- /**
- * Client code should not call {@code onStartCommand} directly.
- */
- @Override
- public int onStartCommand(final Intent intent, int flags, final int startId) {
- if (ManifestInfo.getPushType() == PushType.NONE) {
- PLog.e(TAG, "Started push service even though no push service is enabled: " + intent);
- }
-
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- handler.handlePush(intent);
- } finally {
- dispose(intent);
- stopSelf(startId);
- }
- }
- });
-
- return START_NOT_STICKY;
- }
-
- /**
- * Client code should not call {@code onBind} directly.
- */
- @Override
- public IBinder onBind(Intent intent) {
- throw new IllegalArgumentException("You cannot bind directly to the PushService. "
- + "Use PushService.subscribe instead.");
- }
-
- /**
- * Client code should not call {@code onDestroy} directly.
- */
- @Override
- public void onDestroy() {
- if (executor != null) {
- executor.shutdown();
- executor = null;
- handler = null;
- }
-
- dispatchOnServiceDestroyed(this);
- super.onDestroy();
- }
-}
diff --git a/Parse/src/main/java/com/parse/PushServiceApi26.java b/Parse/src/main/java/com/parse/PushServiceApi26.java
deleted file mode 100644
index cf4633e65..000000000
--- a/Parse/src/main/java/com/parse/PushServiceApi26.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2015-present, Parse, LLC.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-package com.parse;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * A JobService that is triggered by push notifications on Oreo+.
- * Read {@link PushServiceUtils} and {@link PushService} for info and docs.
- * This is already set-up in our own manifest.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public final class PushServiceApi26 extends JobService {
- private static final String TAG = PushServiceApi26.class.getSimpleName();
- private static final String INTENT_KEY = "intent";
- private static final int JOB_SERVICE_ID = 999;
-
- static boolean run(Context context, Intent intent) {
- JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- // Execute in the next second.
- Bundle extra = new Bundle(1);
- extra.putParcelable(INTENT_KEY, intent);
- ComponentName component = new ComponentName(context, PushServiceApi26.class);
- int did = scheduler.schedule(new JobInfo.Builder(JOB_SERVICE_ID, component)
- .setMinimumLatency(1L)
- .setOverrideDeadline(1000L)
- .setRequiresCharging(false)
- .setRequiresBatteryNotLow(false)
- .setRequiresStorageNotLow(false)
- .setTransientExtras(extra)
- .build());
- return did == JobScheduler.RESULT_SUCCESS;
- }
-
- // We delegate the intent to a PushHandler running in a streamlined executor.
- private ExecutorService executor;
- private PushHandler handler;
- private int jobsCount;
-
- // Our manifest file is OK.
- static boolean isSupported() {
- return true;
- }
-
- @Override
- public boolean onStartJob(final JobParameters jobParameters) {
- if (ParsePlugins.get() == null) {
- PLog.e(TAG, "The Parse push service cannot start because Parse.initialize "
- + "has not yet been called. If you call Parse.initialize from "
- + "an Activity's onCreate, that call should instead be in the "
- + "Application.onCreate. Be sure your Application class is registered "
- + "in your AndroidManifest.xml with the android:name property of your "
- + " tag.");
- return false;
- }
-
- final Bundle params = jobParameters.getTransientExtras();
- final Intent intent = params.getParcelable(INTENT_KEY);
- jobsCount++;
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- try {
- getHandler().handlePush(intent);
- } finally {
- jobFinished(jobParameters, false);
- jobsCount--;
- if (jobsCount == 0) {
- tearDown();
- }
- }
- }
- });
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters jobParameters) {
- // Something went wrong before jobFinished(). Try rescheduling.
- return true;
- }
-
- private Executor getExecutor() {
- if (executor == null) executor = Executors.newSingleThreadExecutor();
- return executor;
- }
-
- private PushHandler getHandler() {
- if (handler == null) handler = PushServiceUtils.createPushHandler();
- return handler;
- }
-
- private void tearDown() {
- if (executor != null) executor.shutdown();
- executor = null;
- handler = null;
- }
-}
diff --git a/Parse/src/main/java/com/parse/PushServiceUtils.java b/Parse/src/main/java/com/parse/PushServiceUtils.java
deleted file mode 100644
index 5b8b5d15c..000000000
--- a/Parse/src/main/java/com/parse/PushServiceUtils.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2015-present, Parse, LLC.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-package com.parse;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.support.annotation.NonNull;
-
-import bolts.Task;
-
-
-/**
- * Helper class mostly used to access and wake the push dispatching class, plus some other utilities.
- *
- * Android O introduces limitations over Context.startService. If the app is currently considered
- * in background, the call will result in a crash. The only reliable solutions are either using
- * Context.startServiceInForeground, which does not fit our case, or move to the JobScheduler
- * engine, which is what we do here for Oreo, launching {@link PushServiceApi26}.
- *
- * Pre-oreo, we just launch {@link PushService}.
- *
- * See:
- * https://developer.android.com/about/versions/oreo/background.html
- * https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html
- */
-abstract class PushServiceUtils {
- private static final boolean USE_JOBS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-
- /**
- * Wakes the PushService class by running it either as a Service or as a scheduled job
- * depending on API level.
- *
- * @param context calling context
- * @param intent non-null intent to be passed to the PushHandlers
- * @return true if service could be launched
- */
- public static boolean runService(Context context, @NonNull Intent intent) {
- if (USE_JOBS) {
- return PushServiceApi26.run(context, intent);
- } else {
- return PushService.run(context, intent);
- }
- }
-
- // Checks the manifest file.
- static boolean isSupported() {
- if (USE_JOBS) {
- return PushServiceApi26.isSupported();
- } else {
- return PushService.isSupported();
- }
- }
-
- // Some handlers might need initialization.
- static Task initialize() {
- return createPushHandler().initialize();
- }
-
- static PushHandler createPushHandler() {
- return PushHandler.Factory.create(ManifestInfo.getPushType());
- }
-}
diff --git a/Parse/src/main/java/com/parse/PushType.java b/Parse/src/main/java/com/parse/PushType.java
deleted file mode 100644
index 7edcacd51..000000000
--- a/Parse/src/main/java/com/parse/PushType.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2015-present, Parse, LLC.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-package com.parse;
-
-import java.util.List;
-
-/** package */ enum PushType {
- NONE("none"),
- GCM("gcm");
-
- private final String pushType;
-
- PushType(String pushType) {
- this.pushType = pushType;
- }
-
- static PushType fromString(String pushType) {
- if ("none".equals(pushType)) {
- return PushType.NONE;
- } else if ("gcm".equals(pushType)) {
- return PushType.GCM;
- } else {
- return null;
- }
- }
-
- @Override
- public String toString() {
- return pushType;
- }
-
- // Preference ordered list.
- // TODO: let someone inject here if we want public handlers
- static PushType[] types() {
- return new PushType[]{ GCM, NONE };
- }
-}
diff --git a/Parse/src/test/java/com/parse/ParseInstallationTest.java b/Parse/src/test/java/com/parse/ParseInstallationTest.java
index a5617f023..a8a8a4233 100644
--- a/Parse/src/test/java/com/parse/ParseInstallationTest.java
+++ b/Parse/src/test/java/com/parse/ParseInstallationTest.java
@@ -285,32 +285,6 @@ public void testUpdateBeforeSave() throws Exception {
// TODO(mengyan): Add other testUpdateBeforeSave cases to cover all branches
- @Test
- public void testPushType() throws Exception {
- ParseInstallation installation = new ParseInstallation();
- installation.setPushType(PushType.GCM);
-
- assertEquals(PushType.GCM, installation.getPushType());
-
- installation.removePushType();
-
- assertNull(installation.getPushType());
- // Make sure we add the pushType to operationSetQueue instead of serverData
- assertEquals(1, installation.operationSetQueue.getLast().size());
- }
-
- @Test
- public void testPushTypeWithNullPushType() throws Exception {
- ParseInstallation installation = new ParseInstallation();
- installation.setPushType(PushType.GCM);
-
- assertEquals(PushType.GCM, installation.getPushType());
-
- installation.setPushType(null);
-
- assertEquals(PushType.GCM, installation.getPushType());
- }
-
@Test
public void testDeviceToken() throws Exception {
ParseInstallation installation = new ParseInstallation();
diff --git a/Parse/src/test/java/com/parse/PushHandlerTest.java b/Parse/src/test/java/com/parse/PushHandlerTest.java
deleted file mode 100644
index eccdf6f69..000000000
--- a/Parse/src/test/java/com/parse/PushHandlerTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.parse;
-
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-public class PushHandlerTest {
-
- @Test
- public void testFactory() {
- PushHandler handler = PushHandler.Factory.create(PushType.NONE);
- assertTrue(handler instanceof PushHandler.FallbackHandler);
-
- handler = PushHandler.Factory.create(PushType.GCM);
- assertTrue(handler instanceof GcmPushHandler);
- }
-
- @Test
- public void testFallbackHandler() {
- PushHandler handler = PushHandler.Factory.create(PushType.NONE);
- assertNull(handler.getWarningMessage(PushHandler.SupportLevel.SUPPORTED));
- assertNull(handler.getWarningMessage(PushHandler.SupportLevel.MISSING_OPTIONAL_DECLARATIONS));
- assertNull(handler.getWarningMessage(PushHandler.SupportLevel.MISSING_REQUIRED_DECLARATIONS));
- assertTrue(handler.initialize().isCompleted());
- assertEquals(handler.isSupported(), PushHandler.SupportLevel.SUPPORTED);
- }
-}
diff --git a/Parse/src/test/java/com/parse/PushServiceTest.java b/Parse/src/test/java/com/parse/PushServiceTest.java
deleted file mode 100644
index 2f2615d71..000000000
--- a/Parse/src/test/java/com/parse/PushServiceTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.parse;
-
-
-import android.content.Intent;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ServiceController;
-import org.robolectric.annotation.Config;
-
-import bolts.Task;
-import bolts.TaskCompletionSource;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-@RunWith(RobolectricTestRunner.class)
-@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION)
-public class PushServiceTest extends ResetPluginsParseTest {
-
- private PushService service;
- private ServiceController controller;
- private PushHandler handler;
- private PushService.ServiceLifecycleCallbacks callbacks;
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
- callbacks = mock(PushService.ServiceLifecycleCallbacks.class);
- PushService.registerServiceLifecycleCallbacks(callbacks);
-
- controller = Robolectric.buildService(PushService.class);
- service = controller.get();
- handler = mock(PushHandler.class);
- service.setPushHandler(handler);
-
- Parse.Configuration.Builder builder = new Parse.Configuration.Builder(service);
- ParsePlugins.initialize(service, builder.build());
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- PushService.unregisterServiceLifecycleCallbacks(callbacks);
- }
-
- @Test
- public void testOnCreateWithoutInit() {
- ParsePlugins.reset();
- controller.create();
- verify(callbacks, never()).onServiceCreated(service);
- }
-
- @Test
- public void testOnCreate() {
- controller.create();
- verify(callbacks, times(1)).onServiceCreated(service);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testCannotBind() {
- controller.create().bind();
- }
-
- @Test
- public void testStartCommand() throws Exception {
- controller.create();
- service.setPushHandler(handler); // reset handler to our mock
-
- final TaskCompletionSource tcs = new TaskCompletionSource<>();
- final Task handleTask = tcs.getTask();
- doAnswer(new Answer() {
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- tcs.setResult(null);
- return null;
- }
- }).when(handler).handlePush(any(Intent.class));
-
- controller.startCommand(0, 0);
- handleTask.waitForCompletion();
-
- verify(callbacks, times(1)).onServiceCreated(service);
- verify(handler, times(1)).handlePush(any(Intent.class));
- }
-
- @Test
- public void testDestroy() {
- controller.create();
- controller.startCommand(0, 0);
- controller.destroy();
- verify(callbacks, times(1)).onServiceDestroyed(service);
- }
-}
diff --git a/Parse/src/test/java/com/parse/PushServiceUtilsTest.java b/Parse/src/test/java/com/parse/PushServiceUtilsTest.java
deleted file mode 100644
index 0d8b22efc..000000000
--- a/Parse/src/test/java/com/parse/PushServiceUtilsTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.parse;
-
-
-import android.content.Intent;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ServiceController;
-import org.robolectric.annotation.Config;
-
-import bolts.Task;
-import bolts.TaskCompletionSource;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-public class PushServiceUtilsTest {
-
- @Test
- public void testDefaultHandler() {
- ManifestInfo.setPushType(PushType.NONE);
- PushHandler handler = PushServiceUtils.createPushHandler();
- assertTrue(handler instanceof PushHandler.FallbackHandler);
-
- ManifestInfo.setPushType(PushType.GCM);
- handler = PushServiceUtils.createPushHandler();
- assertTrue(handler instanceof GcmPushHandler);
- }
-
-}
diff --git a/build.gradle b/build.gradle
index c0333ade0..1b255b0d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
+ classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2'
}
}
@@ -24,9 +25,12 @@ allprojects {
ext {
compileSdkVersion = 27
- buildToolsVersion = "27.0.0"
- supportLibVersion = '27.0.1'
+ commonLibVersion = "1.17.0-SNAPSHOT"
+
+ supportLibVersion = '27.1.0'
+ googleLibVersion = '12.0.1'
+ firebaseJobdispatcherVersion = '0.8.5'
minSdkVersion = 14
targetSdkVersion = 27
diff --git a/fcm/.gitignore b/fcm/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/fcm/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/fcm/README.md b/fcm/README.md
new file mode 100644
index 000000000..b1f5983e9
--- /dev/null
+++ b/fcm/README.md
@@ -0,0 +1,67 @@
+# Parse SDK Android FCM
+FCM support for Parse Android apps
+
+## Setup
+
+### Installation
+
+Add dependency to the application level `build.gradle` file.
+
+```groovy
+dependencies {
+ implementation 'com.parse:parse-android:latest.version.here'
+ implementation 'com.parse:parse-android-fcm:latest.version.here'
+}
+```
+Then, follow Google's docs for [setting up an Firebase app](https://firebase.google.com/docs/android/setup). Although the steps are different for setting up FCM with Parse, it is also a good idea to read over the [Firebase FCM Setup](https://firebase.google.com/docs/cloud-messaging/android/client).
+
+You will then need to register some things in your manifest, specifically:
+```xml
+
+
+
+
+
+```
+where `MyFirebaseInstanceIdService` is your own custom class which extends `ParseFirebaseInstanceIdService`.
+
+Additional, you will register:
+
+```xml
+
+
+
+
+
+```
+where `MyFirebaseMessagingService` extends `ParseFirebaseMessagingService`
+
+After these services are registered in the Manifest, you then need to register your push broadcast receiver:
+```xml
+
+
+
+
+
+
+
+```
+
+## Custom Notifications
+If you need to customize the notification that is sent out from a push, you can do so easily by extending `ParsePushBroadcastReceiver` with your own class and registering it instead in the Manifest.
+
+## Instance ID Service
+If you need to store the FCM token elsewhere outside of Parse, you can create your own implementation of the `FirebaseInstanceIdService`, just make sure you are either extending `ParseFirebaseInstanceIdService` or are calling `ParseFCM.scheduleTokenUpload(getApplicationContext());` in the `onTokenRefresh` method.
+
+## License
+ Copyright (c) 2015-present, Parse, LLC.
+ All rights reserved.
+
+ This source code is licensed under the BSD-style license found in the
+ LICENSE file in the root directory of this source tree. An additional grant
+ of patent rights can be found in the PATENTS file in the same directory.
\ No newline at end of file
diff --git a/fcm/build.gradle b/fcm/build.gradle
new file mode 100644
index 000000000..99b3e917e
--- /dev/null
+++ b/fcm/build.gradle
@@ -0,0 +1,49 @@
+apply plugin: 'com.android.library'
+
+version = rootProject.ext.commonLibVersion
+group = 'com.parse'
+
+ext {
+ projDescription = 'Parse Android FCM support.'
+ artifact = 'parse-fcm-android'
+ projName = 'Parse-Android'
+ gitLink = 'https://github.com/parse-community/Parse-SDK-Android'
+}
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ packagingOptions {
+ exclude '**/BuildConfig.class'
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ api "com.google.firebase:firebase-messaging:$googleLibVersion"
+ api "com.firebase:firebase-jobdispatcher:$firebaseJobdispatcherVersion"
+ implementation project(':Parse')
+}
+
+apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
diff --git a/fcm/proguard-rules.pro b/fcm/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/fcm/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/fcm/src/main/AndroidManifest.xml b/fcm/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..d7d51629c
--- /dev/null
+++ b/fcm/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fcm/src/main/java/com/parse/fcm/ParseFCM.java b/fcm/src/main/java/com/parse/fcm/ParseFCM.java
new file mode 100644
index 000000000..c6e296737
--- /dev/null
+++ b/fcm/src/main/java/com/parse/fcm/ParseFCM.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse.fcm;
+
+import android.content.Context;
+
+import com.firebase.jobdispatcher.Constraint;
+import com.firebase.jobdispatcher.FirebaseJobDispatcher;
+import com.firebase.jobdispatcher.GooglePlayDriver;
+import com.firebase.jobdispatcher.Job;
+import com.firebase.jobdispatcher.RetryStrategy;
+
+public class ParseFCM {
+
+ static final String TAG = "ParseFCM";
+
+ private static final String JOB_TAG_UPLOAD_TOKEN = "upload-token";
+
+ /**
+ * You can call this manually if you are overriding the {@link com.google.firebase.iid.FirebaseInstanceIdService}
+ * @param context context
+ */
+ public static void scheduleTokenUpload(Context context) {
+ FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context.getApplicationContext()));
+ Job job = dispatcher.newJobBuilder()
+ .setRecurring(false)
+ .setReplaceCurrent(true)
+ // retry with exponential backoff
+ .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
+ .setConstraints(
+ // only run on a network
+ Constraint.ON_ANY_NETWORK
+ )
+ .setService(ParseFirebaseJobService.class) // the JobService that will be called
+ .setTag(JOB_TAG_UPLOAD_TOKEN) // uniquely identifies the job
+ .build();
+
+ dispatcher.mustSchedule(job);
+ }
+}
diff --git a/fcm/src/main/java/com/parse/fcm/ParseFirebaseInstanceIdService.java b/fcm/src/main/java/com/parse/fcm/ParseFirebaseInstanceIdService.java
new file mode 100644
index 000000000..d068b5ce1
--- /dev/null
+++ b/fcm/src/main/java/com/parse/fcm/ParseFirebaseInstanceIdService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse.fcm;
+
+import android.support.annotation.CallSuper;
+
+import com.google.firebase.iid.FirebaseInstanceIdService;
+import com.parse.ParseInstallation;
+
+/**
+ * Assures the {@link ParseInstallation#getDeviceToken()} stays up to date. If you need to do custom things with the token, make sure you extend this
+ * class and call super.
+ */
+public class ParseFirebaseInstanceIdService extends FirebaseInstanceIdService {
+
+ @CallSuper
+ @Override
+ public void onTokenRefresh() {
+ super.onTokenRefresh();
+ ParseFCM.scheduleTokenUpload(getApplicationContext());
+ }
+}
diff --git a/fcm/src/main/java/com/parse/fcm/ParseFirebaseJobService.java b/fcm/src/main/java/com/parse/fcm/ParseFirebaseJobService.java
new file mode 100644
index 000000000..02b15db38
--- /dev/null
+++ b/fcm/src/main/java/com/parse/fcm/ParseFirebaseJobService.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse.fcm;
+
+import com.firebase.jobdispatcher.JobParameters;
+import com.firebase.jobdispatcher.JobService;
+import com.google.firebase.iid.FirebaseInstanceId;
+import com.parse.PLog;
+import com.parse.ParseException;
+import com.parse.ParseInstallation;
+import com.parse.SaveCallback;
+
+/**
+ * Handles saving the FCM token to the {@link ParseInstallation} in the background
+ */
+public class ParseFirebaseJobService extends JobService {
+
+ @Override
+ public boolean onStartJob(final JobParameters job) {
+ PLog.v(ParseFCM.TAG, "Updating FCM token");
+ ParseInstallation installation = ParseInstallation.getCurrentInstallation();
+ String token = FirebaseInstanceId.getInstance().getToken();
+ if (installation != null && token != null) {
+ installation.setDeviceToken(token);
+ //even though this is FCM, calling it gcm will work on the backend
+ installation.setPushType("gcm");
+ installation.saveInBackground(new SaveCallback() {
+ @Override
+ public void done(ParseException e) {
+ if (e == null) {
+ PLog.v(ParseFCM.TAG, "FCM token saved to installation");
+ jobFinished(job, false);
+ } else {
+ PLog.e(ParseFCM.TAG, "FCM token upload failed", e);
+ jobFinished(job, true);
+ }
+ }
+ });
+ return true;
+ }
+ return false; // Answers the question: "Is there still work going on?"
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters job) {
+ return true; // Answers the question: "Should this job be retried?"
+ }
+}
diff --git a/fcm/src/main/java/com/parse/fcm/ParseFirebaseMessagingService.java b/fcm/src/main/java/com/parse/fcm/ParseFirebaseMessagingService.java
new file mode 100644
index 000000000..cbf94924b
--- /dev/null
+++ b/fcm/src/main/java/com/parse/fcm/ParseFirebaseMessagingService.java
@@ -0,0 +1,35 @@
+package com.parse.fcm;
+
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
+import com.parse.PLog;
+import com.parse.PushRouter;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ParseFirebaseMessagingService extends FirebaseMessagingService {
+
+ @Override
+ public void onMessageReceived(RemoteMessage remoteMessage) {
+ super.onMessageReceived(remoteMessage);
+ PLog.v(ParseFCM.TAG, "onMessageReceived");
+
+ String pushId = remoteMessage.getData().get("push_id");
+ String timestamp = remoteMessage.getData().get("time");
+ String dataString = remoteMessage.getData().get("data");
+ String channel = remoteMessage.getData().get("channel");
+
+ JSONObject data = null;
+ if (dataString != null) {
+ try {
+ data = new JSONObject(dataString);
+ } catch (JSONException e) {
+ PLog.e(ParseFCM.TAG, "Ignoring push because of JSON exception while processing: " + dataString, e);
+ return;
+ }
+ }
+
+ PushRouter.getInstance().handlePush(pushId, timestamp, channel, data);
+ }
+}
diff --git a/gcm/.gitignore b/gcm/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/gcm/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/gcm/README.md b/gcm/README.md
new file mode 100644
index 000000000..6aceccf5b
--- /dev/null
+++ b/gcm/README.md
@@ -0,0 +1,96 @@
+# Parse SDK Android GCM
+GCM support for Parse Android apps
+
+## Deprecated
+Please note that GCM is deprecated in favor of FCM. This module exists as a backwards compatible solution for projects already using GCM. New apps should instead use FCM.
+
+## Setup
+
+### Installation
+
+Add dependency to the application level `build.gradle` file.
+
+```groovy
+dependencies {
+ implementation 'com.parse:parse-android:latest.version.here'
+ implementation 'com.parse:parse-android-gcm:latest.version.here'
+}
+```
+You will then need to register some things in your manifest, firstly, the GCM sender ID:
+```xml
+
+```
+The sender ID should be all numbers. Make sure you are keeping the `id:` in the front
+
+Next:
+```xml
+
+
+
+
+
+
+```
+And to listen for the pushes from GCM:
+```xml
+
+
+
+
+
+```
+And finally, to register the device for GCM pushes:
+```xml
+
+
+
+
+
+```
+
+After these services are registered in the Manifest, you then need to register your push broadcast receiver:
+```xml
+
+
+
+
+
+
+
+```
+After all this, you will need to register GCM in your `Application.onCreate()` like so:
+```java
+@Override
+public void onCreate() {
+ super.onCreate();
+ Parse.Configuration configuration = new Parse.Configuration.Builder(this)
+ //...
+ .build();
+ Parse.initialize(configuration);
+ ParseGCM.register(this);
+}
+```
+
+After this, you are all set.
+
+## Custom Notifications
+If you need to customize the notification that is sent out from a push, you can do so easily by extending `ParsePushBroadcastReceiver` with your own class and registering it instead in the Manifest.
+
+## License
+ Copyright (c) 2015-present, Parse, LLC.
+ All rights reserved.
+
+ This source code is licensed under the BSD-style license found in the
+ LICENSE file in the root directory of this source tree. An additional grant
+ of patent rights can be found in the PATENTS file in the same directory.
\ No newline at end of file
diff --git a/gcm/build.gradle b/gcm/build.gradle
new file mode 100644
index 000000000..6834ab90f
--- /dev/null
+++ b/gcm/build.gradle
@@ -0,0 +1,49 @@
+apply plugin: 'com.android.library'
+
+version = rootProject.ext.commonLibVersion
+group = 'com.parse'
+
+ext {
+ projDescription = 'Parse Android GCM support.'
+ artifact = 'parse-gcm-android'
+ projName = 'Parse-Android'
+ gitLink = 'https://github.com/parse-community/Parse-SDK-Android'
+}
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ packagingOptions {
+ exclude '**/BuildConfig.class'
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ api "com.google.android.gms:play-services-gcm:$googleLibVersion"
+ api "com.firebase:firebase-jobdispatcher:$firebaseJobdispatcherVersion"
+ implementation project(':Parse')
+}
+
+apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
diff --git a/gcm/proguard-rules.pro b/gcm/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/gcm/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/gcm/src/main/AndroidManifest.xml b/gcm/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..2c9bcc51c
--- /dev/null
+++ b/gcm/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gcm/src/main/java/com/parse/gcm/ParseGCM.java b/gcm/src/main/java/com/parse/gcm/ParseGCM.java
new file mode 100644
index 000000000..6ee36eca6
--- /dev/null
+++ b/gcm/src/main/java/com/parse/gcm/ParseGCM.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse.gcm;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import com.firebase.jobdispatcher.Constraint;
+import com.firebase.jobdispatcher.FirebaseJobDispatcher;
+import com.firebase.jobdispatcher.GooglePlayDriver;
+import com.firebase.jobdispatcher.Job;
+import com.firebase.jobdispatcher.RetryStrategy;
+import com.parse.ManifestInfo;
+import com.parse.PLog;
+
+/**
+ * Entry point into setting up Parse GCM Push
+ */
+public class ParseGCM {
+
+ private static final String SENDER_ID_EXTRA = "com.parse.push.gcm_sender_id";
+
+ static final String TAG = "ParseGCM";
+
+ private static final String JOB_TAG_REGISTER = "register";
+
+ /**
+ * Register your app to start receiving GCM pushes
+ *
+ * @param context context
+ */
+ public static void register(Context context) {
+ //kicks off the background job
+ PLog.v(TAG, "Scheduling job to register Parse GCM");
+ FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context.getApplicationContext()));
+ Job job = dispatcher.newJobBuilder()
+ .setRecurring(false)
+ .setReplaceCurrent(true)
+ // retry with exponential backoff
+ .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
+ .setConstraints(
+ // only run on a network
+ Constraint.ON_ANY_NETWORK
+ )
+ .setService(ParseGCMJobService.class) // the JobService that will be called
+ .setTag(JOB_TAG_REGISTER) // uniquely identifies the job
+ .build();
+
+ dispatcher.mustSchedule(job);
+ }
+
+ @Nullable
+ static String gcmSenderFromManifest(Context context) {
+ // Look for an element like this as a child of the element:
+ //
+ //
+ //
+ // The reason why the "id:" prefix is necessary is because Android treats any metadata value
+ // that is a string of digits as an integer. So the call to Bundle.getString() will actually
+ // return null for `android:value="567327206255"`. Additionally, Bundle.getInteger() returns
+ // a 32-bit integer. For `android:value="567327206255"`, this returns a truncated integer
+ // because 567327206255 is larger than the largest 32-bit integer.
+ Bundle metaData = ManifestInfo.getApplicationMetadata(context);
+ String senderID = null;
+
+ if (metaData != null) {
+ Object senderIDExtra = metaData.get(SENDER_ID_EXTRA);
+
+ if (senderIDExtra != null) {
+ senderID = actualSenderIDFromExtra(senderIDExtra);
+
+ if (senderID == null) {
+ PLog.e(TAG, "Found " + SENDER_ID_EXTRA + " element with value \"" +
+ senderIDExtra.toString() + "\", but the value is missing the expected \"id:\" " +
+ "prefix.");
+ return null;
+ }
+ }
+ }
+
+ if (senderID == null) {
+ PLog.e(TAG, "You must provide " + SENDER_ID_EXTRA + " in your AndroidManifest.xml\n" +
+ "Make sure to prefix with the value with id:\n\n" +
+ "\" />");
+ return null;
+ }
+ return senderID;
+ }
+
+ private static String actualSenderIDFromExtra(Object senderIDExtra) {
+ if (!(senderIDExtra instanceof String)) {
+ return null;
+ }
+
+ String senderID = (String) senderIDExtra;
+ if (!senderID.startsWith("id:")) {
+ return null;
+ }
+
+ return senderID.substring(3);
+ }
+}
diff --git a/gcm/src/main/java/com/parse/gcm/ParseGCMInstanceIDListenerService.java b/gcm/src/main/java/com/parse/gcm/ParseGCMInstanceIDListenerService.java
new file mode 100644
index 000000000..b4d2ad17a
--- /dev/null
+++ b/gcm/src/main/java/com/parse/gcm/ParseGCMInstanceIDListenerService.java
@@ -0,0 +1,21 @@
+package com.parse.gcm;
+
+import com.firebase.jobdispatcher.Constraint;
+import com.firebase.jobdispatcher.FirebaseJobDispatcher;
+import com.firebase.jobdispatcher.GooglePlayDriver;
+import com.firebase.jobdispatcher.Job;
+import com.firebase.jobdispatcher.RetryStrategy;
+import com.google.android.gms.iid.InstanceIDListenerService;
+
+/**
+ * Listens for GCM token refreshes and kicks off a background job to save the token
+ */
+public class ParseGCMInstanceIDListenerService extends InstanceIDListenerService {
+
+ @Override
+ public void onTokenRefresh() {
+ super.onTokenRefresh();
+
+ ParseGCM.register(getApplicationContext());
+ }
+}
diff --git a/gcm/src/main/java/com/parse/gcm/ParseGCMJobService.java b/gcm/src/main/java/com/parse/gcm/ParseGCMJobService.java
new file mode 100644
index 000000000..7138e83ca
--- /dev/null
+++ b/gcm/src/main/java/com/parse/gcm/ParseGCMJobService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse.gcm;
+
+import com.firebase.jobdispatcher.JobParameters;
+import com.firebase.jobdispatcher.JobService;
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+import com.parse.PLog;
+import com.parse.ParseInstallation;
+
+import java.util.concurrent.Callable;
+
+import bolts.Task;
+
+/**
+ * Handles saving the GCM token to the Parse Installation
+ */
+public class ParseGCMJobService extends JobService {
+
+ @Override
+ public boolean onStartJob(final JobParameters job) {
+ PLog.v(ParseGCM.TAG, "Updating GCM token");
+
+ Task.callInBackground(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ try {
+ InstanceID instanceID = InstanceID.getInstance(getApplicationContext());
+ String senderId = ParseGCM.gcmSenderFromManifest(getApplicationContext());
+ String token = instanceID.getToken(senderId,
+ GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+ ParseInstallation installation = ParseInstallation.getCurrentInstallation();
+ installation.setDeviceToken(token);
+ //even though this is FCM, calling it gcm will work on the backend
+ installation.setPushType("gcm");
+ installation.save();
+ PLog.v(ParseGCM.TAG, "GCM registration success");
+ } catch (Exception e) {
+ PLog.e(ParseGCM.TAG, "GCM registration failed", e);
+ jobFinished(job, true);
+ }
+ return null;
+ }
+ });
+ return true; // Answers the question: "Is there still work going on?"
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters job) {
+ return true; // Answers the question: "Should this job be retried?"
+ }
+}
diff --git a/gcm/src/main/java/com/parse/gcm/ParseGCMListenerService.java b/gcm/src/main/java/com/parse/gcm/ParseGCMListenerService.java
new file mode 100644
index 000000000..e379a531e
--- /dev/null
+++ b/gcm/src/main/java/com/parse/gcm/ParseGCMListenerService.java
@@ -0,0 +1,34 @@
+package com.parse.gcm;
+
+import android.os.Bundle;
+
+import com.google.android.gms.gcm.GcmListenerService;
+import com.parse.PLog;
+import com.parse.PushRouter;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ParseGCMListenerService extends GcmListenerService {
+
+ @Override
+ public void onMessageReceived(String s, Bundle bundle) {
+ super.onMessageReceived(s, bundle);
+ String pushId = bundle.getString("push_id");
+ String timestamp = bundle.getString("time");
+ String dataString = bundle.getString("data");
+ String channel = bundle.getString("channel");
+
+ JSONObject data = null;
+ if (dataString != null) {
+ try {
+ data = new JSONObject(dataString);
+ } catch (JSONException e) {
+ PLog.e(ParseGCM.TAG, "Ignoring push because of JSON exception while processing: " + dataString, e);
+ return;
+ }
+ }
+
+ PushRouter.getInstance().handlePush(pushId, timestamp, channel, data);
+ }
+}
diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle
new file mode 100644
index 000000000..44d0550ec
--- /dev/null
+++ b/gradle/gradle-mvn-push.gradle
@@ -0,0 +1,175 @@
+apply plugin: 'com.jfrog.bintray'
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ def javadoc = task("javadoc${variant.name.capitalize()}", type: Javadoc) {
+ description "Generates Javadoc for $variant.name."
+ destinationDir = rootProject.file("docs/api")
+ source = variant.javaCompiler.source
+ ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
+ doFirst {
+ classpath = files(variant.javaCompiler.classpath.files) + files(ext.androidJar)
+ }
+ options.docletpath = [rootProject.file("./gradle/ExcludeDoclet.jar")]
+ options.doclet = "me.grantland.doclet.ExcludeDoclet"
+
+ options.linksOffline("http://d.android.com/reference", "${android.sdkDirectory}/docs/reference")
+ options.links("http://boltsframework.github.io/docs/android/")
+
+ exclude '**/BuildConfig.java'
+ exclude '**/R.java'
+ exclude '**/internal/**'
+ }
+
+ def javadocJar = task("javadocJar${variant.name.capitalize()}", type: Jar, dependsOn: "javadoc${variant.name.capitalize()}") {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+ }
+
+ artifacts.add('archives', javadocJar)
+}
+
+//region Maven
+
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+def isSnapshot = version.endsWith('-SNAPSHOT')
+def ossrhUsername = hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : System.getenv('CI_NEXUS_USERNAME')
+def ossrhPassword = hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : System.getenv('CI_NEXUS_PASSWORD')
+
+def pomConfig = {
+ licenses {
+ license {
+ name 'BSD License'
+ url 'https://github.com/parse-community/Parse-SDK-Android/blob/master/LICENSE'
+ distribution 'repo'
+ }
+ }
+
+ scm {
+ connection 'scm:git@github.com:parse-community/Parse-SDK-Android.git'
+ developerConnection 'scm:git@github.com:parse-community/Parse-SDK-Android.git'
+ url gitLink
+ }
+
+ developers {
+ developer {
+ id 'parse'
+ name 'Parse'
+ }
+ }
+}
+
+
+uploadArchives {
+ repositories.mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ def basePom = {
+ name projName
+ artifactId = artifact
+ packaging 'aar'
+ description projDescription
+ url gitLink
+ }
+
+ pom.project basePom << pomConfig
+ }
+}
+
+signing {
+ required { !isSnapshot && gradle.taskGraph.hasTask("uploadArchives") }
+ sign configurations.archives
+}
+
+task androidSourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.sourceFiles
+}
+
+artifacts {
+ archives androidSourcesJar
+}
+
+//endregion
+
+// Requires apply plugin: 'com.jfrog.bintray'
+
+bintray {
+ user = System.getenv('BINTRAY_USER')
+ key = System.getenv('BINTRAY_API_KEY')
+
+ publications = ["mavenAar"]
+
+ publish = true
+ pkg {
+ repo = 'maven'
+ name = 'com.parse:parse-android'
+ userOrg = 'parse'
+ licenses = ['BSD License']
+ vcsUrl = 'https://github.com/parse-community/Parse-SDK-Android'
+ version {
+ name = project.version
+ desc = projDescription
+ released = new Date()
+ vcsTag = project.version
+
+ // Sonatype username/passwrod must be set for this operation to happen
+ mavenCentralSync {
+ sync = true
+ user = ossrhUsername
+ password = ossrhPassword
+ close = '1' // release automatically
+ }
+ }
+ }
+}
+
+// Create the publication with the pom configuration:
+apply plugin: 'digital.wup.android-maven-publish'
+
+publishing {
+ publications {
+ mavenAar(MavenPublication) {
+ from components.android
+ groupId group
+ // We have to specify it here because otherwise Bintray's plugin will assume the artifact's name is Parse
+ artifactId artifact
+ artifacts = [androidSourcesJar, javadocJarRelease, bundleRelease]
+ version version
+
+ }
+
+ }
+}
+
+// End of Bintray plugin
+
+apply plugin: "com.jfrog.artifactory"
+
+artifactory {
+ contextUrl = 'https://oss.jfrog.org'
+ publish {
+ repository {
+ repoKey = 'oss-snapshot-local' // The Artifactory repository key to publish to
+
+ username = System.getenv('BINTRAY_USER')
+ password = System.getenv('BINTRAY_API_KEY')
+ maven = true
+ }
+ defaults {
+ publishArtifacts = true
+ publications('mavenAar')
+ }
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 7cd3dd471..85490d96a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
-include ':Parse'
+include ':Parse', ':fcm', ':gcm'