Bug 1473518 - Abide by Android Oreo background execution limits [Leanplum after upgrade] r?sdaswani draft
authorAndrei Lazar <andrei.a.lazar@softvision.ro>
Fri, 06 Jul 2018 13:21:27 +0300
changeset 814906 2d1f68ef0b0424fe3e0e016e689847792c2c3128
parent 814905 db10c918c784f1671c61d8d05df5ce756a071394
push id115364
push userbmo:andrei.a.lazar@softvision.ro
push dateFri, 06 Jul 2018 10:23:36 +0000
reviewerssdaswani
bugs1473518
milestone63.0a1
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 ***
mobile/android/base/MmaAndroidManifest_services.xml.in
mobile/android/thirdparty/com/leanplum/LeanplumGcmRegistrationJobService.java
mobile/android/thirdparty/com/leanplum/LeanplumNotificationHelper.java
mobile/android/thirdparty/com/leanplum/LeanplumPushInstanceIDService.java
--- 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);
+    }
   }
 }