Bug 1473518 - Abide by Android Oreo background execution limits [Leanplum after upgrade] r?sdaswani
Refactored existing LeanplumPushInstanceIDService to support Oreo background
execution limits in Leanplum after upgrade.
MozReview-Commit-ID: JjUlrOv34KR
***
--- a/mobile/android/base/MmaAndroidManifest_services.xml.in
+++ b/mobile/android/base/MmaAndroidManifest_services.xml.in
@@ -4,16 +4,27 @@
<action android:name="com.leanplum.LeanplumPushListenerService" />
</intent-filter>
</receiver>
<!-- Leanplum Local Push Notification Service-->
<service android:name="com.leanplum.LeanplumLocalPushListenerService" android:exported="false"
android:enabled="true" />
+ <service android:name="com.leanplum.LeanplumPushListenerService" android:exported="false">
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <category android:name="@ANDROID_PACKAGE_NAME@" />
+ </intent-filter>
+ </service>
+
+ <!-- Leanplum GCM Registration Job Service. -->
+ <service android:name="com.leanplum.LeanplumGcmRegistrationJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
+
<!-- Leanplum GCM Instance ID Service -->
<service android:name="com.leanplum.LeanplumPushInstanceIDService" android:exported="false"
android:enabled="true">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/leanplum/LeanplumGcmRegistrationJobService.java
@@ -0,0 +1,25 @@
+package com.leanplum;
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+/**
+ * Leanplum GCM registration Job Service to start registration service.
+ *
+ * @author Anna Orlova
+ */
+@TargetApi(21)
+public class LeanplumGcmRegistrationJobService extends JobService {
+ public static final int JOB_ID = -63722755;
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ LeanplumNotificationHelper.startPushRegistrationService(this, "GCM");
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+}
\ No newline at end of file
--- a/mobile/android/thirdparty/com/leanplum/LeanplumNotificationHelper.java
+++ b/mobile/android/thirdparty/com/leanplum/LeanplumNotificationHelper.java
@@ -19,35 +19,41 @@ package com.leanplum;
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.TypedValue;
import android.widget.RemoteViews;
import com.leanplum.internal.Constants;
import com.leanplum.internal.JsonConverter;
import com.leanplum.internal.Log;
import com.leanplum.utils.BuildUtil;
+import java.util.List;
import java.util.Map;
+import java.util.Random;
+import java.util.TreeSet;
/**
* LeanplumNotificationHelper helper class for push notifications.
*
* @author Anna Orlova
*/
class LeanplumNotificationHelper {
@@ -77,16 +83,89 @@ class LeanplumNotificationHelper {
return new NotificationCompat.Builder(context, channelId);
} else {
Log.w("Failed to post notification, there are no notification channels configured.");
return null;
}
}
/**
+ * Starts push registration service to update GCM/FCM InstanceId token.
+ *
+ * @param context Current application context.
+ * @param providerName Name of push notification provider.
+ */
+ static void startPushRegistrationService(Context context, String providerName) {
+ try {
+ if (context == null) {
+ return;
+ }
+ Log.i("Updating " + providerName + " InstanceId token.");
+ // Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
+ Intent intent = new Intent(context, LeanplumPushRegistrationService.class);
+ context.startService(intent);
+ } catch (Throwable t) {
+ Log.e("Couldn't update " + providerName + " InstanceId token.", t);
+ }
+ }
+
+ /**
+ * Schedule JobService to JobScheduler.
+ *
+ * @param context Current application context.
+ * @param clazz JobService class.
+ * @param jobId JobService id.
+ */
+ @TargetApi(21)
+ static void scheduleJobService(Context context, Class clazz, int jobId) {
+ if (context == null) {
+ return;
+ }
+ ComponentName serviceName = new ComponentName(context, clazz);
+ JobScheduler jobScheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ if (jobScheduler != null) {
+ jobId = verifyJobId(jobScheduler.getAllPendingJobs(), jobId);
+ JobInfo startMyServiceJobInfo = new JobInfo.Builder(jobId, serviceName)
+ .setMinimumLatency(10).build();
+ jobScheduler.schedule(startMyServiceJobInfo);
+ }
+ }
+
+ /**
+ * Verifies that jobId don't present on JobScheduler pending jobs. If jobId present on
+ * JobScheduler pending jobs generates a new one.
+ *
+ * @param allPendingJobs List of current pending jobs.
+ * @param jobId JobService id.
+ * @return jobId if jobId don't present on JobScheduler pending jobs
+ */
+ @TargetApi(21)
+ private static int verifyJobId(List<JobInfo> allPendingJobs, int jobId) {
+ if (allPendingJobs != null && !allPendingJobs.isEmpty()) {
+ TreeSet<Integer> idsSet = new TreeSet<>();
+ for (JobInfo jobInfo : allPendingJobs) {
+ idsSet.add(jobInfo.getId());
+ }
+ if (idsSet.contains(jobId)) {
+ if (idsSet.first() > Integer.MIN_VALUE) {
+ jobId = idsSet.first() - 1;
+ } else if (idsSet.last() < Integer.MIN_VALUE) {
+ jobId = idsSet.last() + 1;
+ } else {
+ while (idsSet.contains(jobId)) {
+ jobId = new Random().nextInt();
+ }
+ }
+ }
+ }
+ return jobId;
+ }
+
+ /**
* If notification channels are supported this method will try to create
* Notification.Builder with default notification channel if default channel id is provided.
* If notification channels not supported this method will return Notification.Builder for
* context.
*
* @param context The application context.
* @param isNotificationChannelSupported True if notification channels are supported.
* @return Notification.Builder for provided context or null.
--- a/mobile/android/thirdparty/com/leanplum/LeanplumPushInstanceIDService.java
+++ b/mobile/android/thirdparty/com/leanplum/LeanplumPushInstanceIDService.java
@@ -16,17 +16,17 @@
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.leanplum;
-import android.content.Intent;
+import android.os.Build;
import com.google.android.gms.iid.InstanceIDListenerService;
import com.leanplum.internal.Log;
/**
* GCM InstanceID listener service to handle creation, rotation, and updating of registration
* tokens.
*
@@ -34,14 +34,20 @@ import com.leanplum.internal.Log;
*/
public class LeanplumPushInstanceIDService extends InstanceIDListenerService {
/**
* Called if InstanceID token is updated. This may occur if the security of the previous token had
* been compromised. This call is initiated by the InstanceID provider.
*/
@Override
public void onTokenRefresh() {
- Log.i("GCM InstanceID token needs an update");
- // Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
- Intent intent = new Intent(this, LeanplumPushRegistrationService.class);
- startService(intent);
+ try {
+ if (Build.VERSION.SDK_INT < 26) {
+ LeanplumNotificationHelper.startPushRegistrationService(this, "GCM");
+ } else {
+ LeanplumNotificationHelper.scheduleJobService(this,
+ LeanplumGcmRegistrationJobService.class, LeanplumGcmRegistrationJobService.JOB_ID);
+ }
+ } catch (Throwable t) {
+ Log.e("Failed to update GCM token.", t);
+ }
}
}