From e264144aea7b7b474792d69802805281b4964dd4 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Mon, 11 Jan 2016 20:49:17 +0100 Subject: [PATCH 1/2] GCM: Wake with exact guarantees for heartbeat This changes how the alarms for heartbeat pings are scheduled. Instead of a repeating, inexact alarm that may be delayed (at least up to 15 minutes), either an exact alarm (below Android 4.4) or an interval is used which ensures that the alarm fires between half the configured interval and the configured interval. This interval allows the OS to optimize alarms a bit. For Android 6.0 further adjustments are probably necessary. --- .../java/org/microg/gms/gcm/McsService.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 08192d0b..c8326b60 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -22,6 +22,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -145,10 +146,25 @@ public class McsService extends Service implements Handler.Callback { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); long delay = getCurrentDelay(); Log.d(TAG, "Scheduling reconnect in " + delay / 1000 + " seconds..."); - alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delay, + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0)); } + public void scheduleHeartbeat(Context context) { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + Log.d(TAG, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); + } else { + // with KitKat, the alarms become inexact by default, but with the newly available setWindow we can get inexact alarms with guarantees. + // Schedule the alarm to fire within the interval [heartbeatMs/2, heartbeatMs] + alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 2, heartbeatMs / 2, + heartbeatIntent); + } + + } + public synchronized static long getCurrentDelay() { long delay = currentDelay == 0 ? 5000 : currentDelay; if (currentDelay < 60000) currentDelay += 10000; @@ -181,7 +197,7 @@ public class McsService extends Service implements Handler.Callback { WakefulBroadcastReceiver.completeWakefulIntent(intent); } } - return START_STICKY; + return START_REDELIVER_INTENT; } private synchronized void connect() { @@ -196,7 +212,7 @@ public class McsService extends Service implements Handler.Callback { inputStream.start(); outputStream.start(); - alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), heartbeatMs, heartbeatIntent); + scheduleHeartbeat(this); } catch (Exception e) { Log.w(TAG, "Exception while connecting!", e); rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); @@ -340,6 +356,7 @@ public class McsService extends Service implements Handler.Callback { ping.last_stream_id_received(inputStream.getStreamId()); } send(ping.build()); + scheduleHeartbeat(this); } else { Log.d(TAG, "Ignoring heartbeat, not connected!"); scheduleReconnect(this); From 5fd376de7ad6c87d14927d81a54628c84403975f Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Mon, 11 Jan 2016 20:54:25 +0100 Subject: [PATCH 2/2] GCM: Consider connection to be dead without ack messages This stores the timestamp of the last ack message that has been received. The connection is considered to be dead if the last ack message has been received more than twice the configured interval ago. --- .../src/main/java/org/microg/gms/gcm/McsService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index c8326b60..df28e07d 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -78,7 +78,8 @@ public class McsService extends Service implements Handler.Callback { public static final int SERVICE_PORT = 5228; private static final String PREF_GCM_HEARTBEAT = "gcm_heartbeat_interval"; - public int heartbeatMs = 60000; + public static int heartbeatMs = 60000; + private static long lastHeartbeatAckElapsedRealtime = -1; private static Socket sslSocket; private static McsInputStream inputStream; @@ -139,7 +140,9 @@ public class McsService extends Service implements Handler.Callback { } public synchronized static boolean isConnected() { - return inputStream != null && inputStream.isAlive() && outputStream != null && outputStream.isAlive(); + return inputStream != null && inputStream.isAlive() && outputStream != null && outputStream.isAlive() + // consider connection to be dead if we did not receive an ack within twice the heartbeat interval + && SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime < 2 * heartbeatMs; } public static void scheduleReconnect(Context context) { @@ -212,6 +215,7 @@ public class McsService extends Service implements Handler.Callback { inputStream.start(); outputStream.start(); + lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); scheduleHeartbeat(this); } catch (Exception e) { Log.w(TAG, "Exception while connecting!", e); @@ -258,6 +262,7 @@ public class McsService extends Service implements Handler.Callback { } private void handleHeartbeatAck(HeartbeatAck ack) { + lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); wakeLock.release(); }