diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/Constants.java b/play-services-core/src/main/java/org/microg/gms/gcm/Constants.java index cc5b7716..71ba882d 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/Constants.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/Constants.java @@ -35,4 +35,9 @@ public class Constants { public static final int MSG_TEARDOWN = 30; public static final int MSG_CONNECT = 40; public static final int MSG_HEARTBEAT = 41; + + public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT"; + public static String ACTION_RECONNECT = "org.microg.gms.gcm.mcs.RECONNECT"; + public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT"; + public static String EXTRA_REASON = "org.microg.gms.gcm.mcs.REASON"; } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsInputStream.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsInputStream.java index 51f6a678..6af11017 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsInputStream.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsInputStream.java @@ -72,6 +72,7 @@ public class McsInputStream extends Thread { try { while (!Thread.currentThread().isInterrupted()) { Message msg = read(); + Log.d(TAG, "Incoming message: " + msg); if (msg != null) { mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_INPUT, msg)); } else { @@ -109,7 +110,7 @@ public class McsInputStream extends Thread { if (!initialized) { try { version = is.read(); - Log.d(TAG, "Reading from MCS version=" + version); + Log.d(TAG, "Reading from MCS version: " + version); initialized = true; } catch (IOException e) { Log.w(TAG, e); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsOutputStream.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsOutputStream.java index c0277d76..e4d1fc2e 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsOutputStream.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsOutputStream.java @@ -80,6 +80,7 @@ public class McsOutputStream extends Thread implements Handler.Callback { case MSG_OUTPUT: try { Message message = (Message) msg.obj; + Log.d(TAG, "Outgoing message: " + message); if (msg.obj instanceof DataMessageStanza) { writeInternal(message, MCS_DATA_MESSAGE_STANZA_TAG); } else if (msg.obj instanceof LoginRequest) { 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 fccf7c23..57833923 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 @@ -17,12 +17,13 @@ package org.microg.gms.gcm; import android.app.AlarmManager; -import android.app.IntentService; import android.app.PendingIntent; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; @@ -48,6 +49,10 @@ import java.util.Collections; import javax.net.ssl.SSLContext; import static android.os.Build.VERSION.SDK_INT; +import static org.microg.gms.gcm.Constants.ACTION_CONNECT; +import static org.microg.gms.gcm.Constants.ACTION_HEARTBEAT; +import static org.microg.gms.gcm.Constants.ACTION_RECONNECT; +import static org.microg.gms.gcm.Constants.EXTRA_REASON; import static org.microg.gms.gcm.Constants.MSG_CONNECT; import static org.microg.gms.gcm.Constants.MSG_HEARTBEAT; import static org.microg.gms.gcm.Constants.MSG_INPUT; @@ -57,12 +62,9 @@ import static org.microg.gms.gcm.Constants.MSG_OUTPUT_ERROR; import static org.microg.gms.gcm.Constants.MSG_OUTPUT_READY; import static org.microg.gms.gcm.Constants.MSG_TEARDOWN; -public class McsService extends IntentService implements Handler.Callback { +public class McsService extends Service implements Handler.Callback { private static final String TAG = "GmsGcmMcsSvc"; - public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT"; - public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT"; - public static final String PREFERENCES_NAME = "mcs"; public static final String PREF_LAST_PERSISTENT_ID = "last_persistent_id"; @@ -81,8 +83,8 @@ public class McsService extends IntentService implements Handler.Callback { private PendingIntent heartbeatIntent; - private static MainThread mainThread; - private static Handler mainHandler; + private static HandlerThread handlerThread; + private static Handler rootHandler; private AlarmManager alarmManager; private PowerManager powerManager; @@ -92,20 +94,16 @@ public class McsService extends IntentService implements Handler.Callback { private Intent connectIntent; - public McsService() { - super(TAG); - } - - private class MainThread extends Thread { + private class HandlerThread extends Thread { @Override public void run() { Looper.prepare(); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs"); wakeLock.setReferenceCounted(false); synchronized (McsService.class) { - mainHandler = new Handler(Looper.myLooper(), McsService.this); + rootHandler = new Handler(Looper.myLooper(), McsService.this); if (connectIntent != null) { - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT, connectIntent)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, connectIntent)); WakefulBroadcastReceiver.completeWakefulIntent(connectIntent); } } @@ -120,27 +118,34 @@ public class McsService extends IntentService implements Handler.Callback { alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); synchronized (McsService.class) { - if (mainThread == null) { - mainThread = new MainThread(); - mainThread.start(); + if (handlerThread == null) { + handlerThread = new HandlerThread(); + handlerThread.start(); } } } + @Override + public IBinder onBind(Intent intent) { + return null; + } + public synchronized static boolean isConnected() { return inputStream != null && inputStream.isAlive() && outputStream != null && outputStream.isAlive(); } public static void scheduleReconnect(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); - alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + getCurrentDelay(), - PendingIntent.getBroadcast(context, 1, new Intent("org.microg.gms.gcm.RECONNECT", null, context, TriggerReceiver.class), 0)); + long delay = getCurrentDelay(); + Log.d(TAG, "Scheduling reconnect in " + delay / 1000 + " seconds..."); + alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delay, + PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0)); } public synchronized static long getCurrentDelay() { long delay = currentDelay == 0 ? 5000 : currentDelay; - if (currentDelay < 60000) currentDelay += 5000; - if (currentDelay >= 60000 && currentDelay < 60000) currentDelay += 60000; + if (currentDelay < 60000) currentDelay += 10000; + if (currentDelay >= 60000 && currentDelay < 600000) currentDelay += 60000; return delay; } @@ -148,15 +153,18 @@ public class McsService extends IntentService implements Handler.Callback { currentDelay = 0; } + @Override - protected void onHandleIntent(Intent intent) { + public int onStartCommand(Intent intent, int flags, int startId) { synchronized (McsService.class) { - if (mainHandler != null) { + if (rootHandler != null) { wakeLock.acquire(); + Object reason = intent == null ? "I am so sticky!" : + intent.hasExtra(EXTRA_REASON) ? intent.getExtras().get(EXTRA_REASON) : intent; if (ACTION_CONNECT.equals(intent.getAction())) { - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT, intent)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, reason)); } else if (ACTION_HEARTBEAT.equals(intent.getAction())) { - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_HEARTBEAT, intent)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_HEARTBEAT, reason)); } WakefulBroadcastReceiver.completeWakefulIntent(intent); } else if (connectIntent == null) { @@ -165,6 +173,7 @@ public class McsService extends IntentService implements Handler.Callback { WakefulBroadcastReceiver.completeWakefulIntent(intent); } } + return START_STICKY; } private synchronized void connect() { @@ -174,15 +183,15 @@ public class McsService extends IntentService implements Handler.Callback { Log.d(TAG, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true); Log.d(TAG, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); - inputStream = new McsInputStream(sslSocket.getInputStream(), mainHandler); - outputStream = new McsOutputStream(sslSocket.getOutputStream(), mainHandler); + inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); + outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); inputStream.start(); outputStream.start(); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), HEARTBEAT_MS, heartbeatIntent); } catch (Exception e) { Log.w(TAG, "Exception while connecting!", e); - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); } } @@ -278,7 +287,7 @@ public class McsService extends IntentService implements Handler.Callback { } private void send(Message message) { - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT, message)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_OUTPUT, message)); } private void sendOutputStream(int what, Object obj) { @@ -286,7 +295,7 @@ public class McsService extends IntentService implements Handler.Callback { if (os != null) { Handler outputHandler = os.getHandler(); if (outputHandler != null) - outputHandler.dispatchMessage(outputHandler.obtainMessage(what, obj)); + outputHandler.sendMessage(outputHandler.obtainMessage(what, obj)); } } @@ -294,17 +303,15 @@ public class McsService extends IntentService implements Handler.Callback { public boolean handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_INPUT: - Log.d(TAG, "Incoming message: " + msg.obj); handleInput((Message) msg.obj); return true; case MSG_OUTPUT: - Log.d(TAG, "Outgoing message: " + msg.obj); sendOutputStream(MSG_OUTPUT, msg.obj); return true; case MSG_INPUT_ERROR: case MSG_OUTPUT_ERROR: Log.d(TAG, "I/O error: " + msg.obj); - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); return true; case MSG_TEARDOWN: Log.d(TAG, "Teardown initiated, reason: " + msg.obj); @@ -326,6 +333,7 @@ public class McsService extends IntentService implements Handler.Callback { send(ping.build()); } else { Log.d(TAG, "Ignoring heartbeat, not connected!"); + scheduleReconnect(this); } return true; case MSG_OUTPUT_READY: @@ -354,7 +362,7 @@ public class McsService extends IntentService implements Handler.Callback { } resetCurrentDelay(); } catch (Exception e) { - mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e)); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java index 5477bd7d..9c77d54b 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java @@ -20,8 +20,8 @@ import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Build; -import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.util.Log; @@ -35,8 +35,16 @@ import java.io.IOException; public class PushRegisterService extends IntentService { private static final String TAG = "GmsGcmRegisterSvc"; + private static final String REMOVED = "%%REMOVED%%"; + private static final String ERROR = "%%ERROR%%"; + public PushRegisterService() { super(TAG); + setIntentRedelivery(false); + } + + private SharedPreferences getSharedPreferences() { + return getSharedPreferences("gcm_registrations", MODE_PRIVATE); } @Override @@ -68,17 +76,20 @@ public class PushRegisterService extends IntentService { PendingIntent pendingIntent = intent.getParcelableExtra("app"); String sender = intent.getStringExtra("sender"); String app = packageFromPendingIntent(pendingIntent); - Bundle extras = intent.getExtras(); - extras.keySet(); - Log.d(TAG, "register[req]: " + extras); + Log.d(TAG, "register[res]: " + intent.toString() + " extras=" + intent.getExtras()); + Intent outIntent = new Intent("com.google.android.c2dm.intent.REGISTRATION"); - outIntent.setPackage(app); - String regId = register(this, app, sender, null, false).token; + String appSignature = PackageUtils.firstSignatureDigest(this, app); + + String regId = register(this, app, appSignature, sender, null, false).token; if (regId != null) { outIntent.putExtra("registration_id", regId); + getSharedPreferences().edit().putString(app + ":" + appSignature, regId).apply(); } else { outIntent.putExtra("error", "SERVICE_NOT_AVAILABLE"); + getSharedPreferences().edit().putString(app + ":" + appSignature, "-").apply(); } + Log.d(TAG, "register[res]: " + outIntent + " extras=" + outIntent.getExtras()); try { if (intent.hasExtra("google.messenger")) { @@ -91,17 +102,19 @@ public class PushRegisterService extends IntentService { } catch (Exception e) { Log.w(TAG, e); } + + outIntent.setPackage(app); sendOrderedBroadcast(outIntent, null); } - public static RegisterResponse register(Context context, String app, String sender, String info, boolean delete) { + public static RegisterResponse register(Context context, String app, String appSignature, String sender, String info, boolean delete) { try { RegisterResponse response = new RegisterRequest() .build(Utils.getBuild(context)) .sender(sender) .info(info) .checkin(LastCheckinInfo.read(context)) - .app(app, PackageUtils.firstSignatureDigest(context, app), PackageUtils.versionCode(context, app)) + .app(app, appSignature, PackageUtils.versionCode(context, app)) .delete(delete) .getResponse(); Log.d(TAG, "received response: " + response); @@ -116,24 +129,27 @@ public class PushRegisterService extends IntentService { private void unregister(Intent intent) { PendingIntent pendingIntent = intent.getParcelableExtra("app"); String app = packageFromPendingIntent(pendingIntent); + Log.d(TAG, "unregister[res]: " + intent.toString() + " extras=" + intent.getExtras()); Intent outIntent = new Intent("com.google.android.c2dm.intent.REGISTRATION"); - outIntent.setPackage(app); + String appSignature = PackageUtils.firstSignatureDigest(this, app); - RegisterResponse response = register(this, app, null, null, true); - if (!app.equals(response.deleted)) { - outIntent.putExtra("error", "SERVICE_NOT_AVAILABLE"); - - long retry = 0; - if (response.retryAfter != null && !response.retryAfter.contains(":")) { - retry = Long.parseLong(response.retryAfter); - } - - if (retry > 0) { - outIntent.putExtra("Retry-After", retry); - } - } else { + if (REMOVED.equals(getSharedPreferences().getString(app + ":" + appSignature, null))) { outIntent.putExtra("unregistered", app); + } else { + RegisterResponse response = register(this, app, appSignature, null, null, true); + if (!app.equals(response.deleted)) { + outIntent.putExtra("error", "SERVICE_NOT_AVAILABLE"); + getSharedPreferences().edit().putString(app + ":" + PackageUtils.firstSignatureDigest(this, app), ERROR).apply(); + + long retry = 0; + if (response.retryAfter != null && !response.retryAfter.contains(":")) { + outIntent.putExtra("Retry-After", Long.parseLong(response.retryAfter)); + } + } else { + outIntent.putExtra("unregistered", app); + getSharedPreferences().edit().putString(app + ":" + PackageUtils.firstSignatureDigest(this, app), REMOVED).apply(); + } } Log.d(TAG, "unregister[res]: " + outIntent.toString() + " extras=" + outIntent.getExtras()); @@ -148,6 +164,8 @@ public class PushRegisterService extends IntentService { } catch (Exception e) { Log.w(TAG, e); } + + outIntent.setPackage(app); sendOrderedBroadcast(outIntent, null); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index f8c78c38..13df88ea 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -26,6 +26,10 @@ import android.util.Log; import org.microg.gms.checkin.LastCheckinInfo; +import static org.microg.gms.gcm.Constants.ACTION_CONNECT; +import static org.microg.gms.gcm.Constants.ACTION_HEARTBEAT; +import static org.microg.gms.gcm.Constants.EXTRA_REASON; + public class TriggerReceiver extends WakefulBroadcastReceiver { private static final String TAG = "GmsGcmTrigger"; private static final String PREF_ENABLE_GCM = "gcm_enable_mcs_service"; @@ -41,19 +45,23 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { } if (LastCheckinInfo.read(context).androidId == 0) { Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); + return; } NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected() || force) { if (!McsService.isConnected() || force) { - startWakefulService(context, new Intent(McsService.ACTION_CONNECT, null, context, McsService.class)); + Log.d(TAG, "Not connected to GCM but should be, asking the service to start up"); + startWakefulService(context, new Intent(ACTION_CONNECT, null, context, McsService.class) + .putExtra(EXTRA_REASON, intent)); } else { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { Log.d(TAG, "Ignoring " + intent + ": service is running. schedule reconnect instead."); McsService.scheduleReconnect(context); } else { Log.d(TAG, "Ignoring " + intent + ": service is running. heartbeat instead."); - startWakefulService(context, new Intent(McsService.ACTION_HEARTBEAT, null, context, McsService.class)); + startWakefulService(context, new Intent(ACTION_HEARTBEAT, null, context, McsService.class) + .putExtra(EXTRA_REASON, intent)); } } } else {