Update portions of GCM implementation

- Fix bug causing unregister to be send multiple times
- More work related to #23, #29 and #31
This commit is contained in:
mar-v-in 2015-10-11 00:46:58 +02:00
parent 02e6ffce4d
commit 766a6a1b47
6 changed files with 100 additions and 59 deletions

View File

@ -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";
}

View File

@ -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);

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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 {