mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2025-01-23 09:27:31 +01:00
GCM: Add support for message acking, deliver to all receivers of package when working permissionless
This commit is contained in:
parent
74c0e28e27
commit
60cc63ed60
@ -30,6 +30,7 @@ public final class GcmConstants {
|
||||
public static final String ACTION_INSTANCE_ID = "com.google.android.gms.iid.InstanceID";
|
||||
|
||||
public static final String EXTRA_APP = "app";
|
||||
public static final String EXTRA_APP_OVERRIDE = "org.microg.gms.gcm.APP_OVERRIDE";
|
||||
public static final String EXTRA_APP_ID = "appid";
|
||||
public static final String EXTRA_APP_VERSION_CODE = "app_ver";
|
||||
public static final String EXTRA_APP_VERSION_NAME = "app_ver_name";
|
||||
|
@ -36,10 +36,12 @@ public final class McsConstants {
|
||||
public static final int MSG_TEARDOWN = 30;
|
||||
public static final int MSG_CONNECT = 40;
|
||||
public static final int MSG_HEARTBEAT = 41;
|
||||
public static final int MSG_ACK = 42;
|
||||
|
||||
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 ACTION_SEND = "org.microg.gms.gcm.mcs.SEND";
|
||||
public static String ACTION_ACK = "org.microg.gms.gcm.mcs.ACK";
|
||||
public static String EXTRA_REASON = "org.microg.gms.gcm.mcs.REASON";
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
@ -51,8 +52,10 @@ import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.gcm.mcs.AppData;
|
||||
import org.microg.gms.gcm.mcs.Close;
|
||||
import org.microg.gms.gcm.mcs.DataMessageStanza;
|
||||
import org.microg.gms.gcm.mcs.Extension;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatAck;
|
||||
import org.microg.gms.gcm.mcs.HeartbeatPing;
|
||||
import org.microg.gms.gcm.mcs.IqStanza;
|
||||
import org.microg.gms.gcm.mcs.LoginRequest;
|
||||
import org.microg.gms.gcm.mcs.LoginResponse;
|
||||
import org.microg.gms.gcm.mcs.Setting;
|
||||
@ -64,6 +67,7 @@ import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
@ -74,6 +78,7 @@ import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.microg.gms.common.ForegroundServiceContext.EXTRA_FOREGROUND;
|
||||
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_COLLAPSE_KEY;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID;
|
||||
@ -82,6 +87,7 @@ import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_TO;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_TTL;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_ACK;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_RECONNECT;
|
||||
@ -91,8 +97,10 @@ import static org.microg.gms.gcm.McsConstants.MCS_CLOSE_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_DATA_MESSAGE_STANZA_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_ACK_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_PING_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_IQ_STANZA_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_REQUEST_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_RESPONSE_TAG;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_ACK;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_CONNECT;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_HEARTBEAT;
|
||||
import static org.microg.gms.gcm.McsConstants.MSG_INPUT;
|
||||
@ -119,6 +127,7 @@ public class McsService extends Service implements Handler.Callback {
|
||||
private static long lastIncomingNetworkRealtime = 0;
|
||||
private static long startTimestamp = 0;
|
||||
public static String activeNetworkPref = null;
|
||||
private AtomicInteger nextMessageId = new AtomicInteger(0x1000000);
|
||||
|
||||
private static Socket sslSocket;
|
||||
private static McsInputStream inputStream;
|
||||
@ -307,6 +316,8 @@ public class McsService extends Service implements Handler.Callback {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_HEARTBEAT, reason));
|
||||
} else if (ACTION_SEND.equals(intent.getAction())) {
|
||||
handleSendMessage(intent);
|
||||
} else if (ACTION_ACK.equals(intent.getAction())) {
|
||||
rootHandler.sendMessage(rootHandler.obtainMessage(MSG_ACK, reason));
|
||||
}
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
||||
} else if (connectIntent == null) {
|
||||
@ -347,12 +358,20 @@ public class McsService extends Service implements Handler.Callback {
|
||||
Log.w(TAG, "Failed to send message, missing package name");
|
||||
return;
|
||||
}
|
||||
if (packageName.equals(getPackageName()) && intent.hasExtra(EXTRA_APP_OVERRIDE)) {
|
||||
packageName = intent.getStringExtra(EXTRA_APP_OVERRIDE);
|
||||
intent.removeExtra(EXTRA_APP_OVERRIDE);
|
||||
}
|
||||
intent.removeExtra(EXTRA_APP);
|
||||
|
||||
int ttl;
|
||||
try {
|
||||
ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL));
|
||||
if (ttl < 0 || ttl > maxTtl) {
|
||||
if (intent.hasExtra(EXTRA_TTL)) {
|
||||
ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL));
|
||||
if (ttl < 0 || ttl > maxTtl) {
|
||||
ttl = maxTtl;
|
||||
}
|
||||
} else {
|
||||
ttl = maxTtl;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
@ -403,7 +422,8 @@ public class McsService extends Service implements Handler.Callback {
|
||||
try {
|
||||
DataMessageStanza msg = new DataMessageStanza.Builder()
|
||||
.sent(System.currentTimeMillis() / 1000L)
|
||||
.id(messageId)
|
||||
.id(Integer.toHexString(nextMessageId.incrementAndGet()))
|
||||
.persistent_id(messageId)
|
||||
.token(collapseKey)
|
||||
.from(from)
|
||||
.reg_id(registrationId)
|
||||
@ -513,9 +533,11 @@ public class McsService extends Service implements Handler.Callback {
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_C2DM_RECEIVE);
|
||||
intent.setPackage(packageName);
|
||||
intent.putExtra(EXTRA_FROM, msg.from);
|
||||
intent.putExtra(EXTRA_MESSAGE_ID, msg.id);
|
||||
if (msg.persistent_id != null) {
|
||||
intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id);
|
||||
}
|
||||
if (app.wakeForDelivery) {
|
||||
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
||||
} else {
|
||||
@ -526,41 +548,52 @@ public class McsService extends Service implements Handler.Callback {
|
||||
intent.putExtra(appData.key, appData.value);
|
||||
}
|
||||
|
||||
String receiverPermission;
|
||||
String receiverPermission = null;
|
||||
try {
|
||||
String name = packageName + ".permission.C2D_MESSAGE";
|
||||
getPackageManager().getPermissionInfo(name, 0);
|
||||
receiverPermission = name;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
receiverPermission = null;
|
||||
PermissionInfo info = getPackageManager().getPermissionInfo(name, 0);
|
||||
if (info.packageName.equals(packageName)) {
|
||||
receiverPermission = name;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Keep null, no valid permission found
|
||||
}
|
||||
|
||||
List<ResolveInfo> infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER);
|
||||
if (infos == null || infos.isEmpty()) {
|
||||
logd("No target for message, wut?");
|
||||
if (receiverPermission == null) {
|
||||
// Without receiver permission, we only restrict by package name
|
||||
logd("Deliver message to all receivers in package " + packageName);
|
||||
intent.setPackage(packageName);
|
||||
sendOrderedBroadcast(intent, null);
|
||||
} else {
|
||||
for (ResolveInfo resolveInfo : infos) {
|
||||
logd("Target: " + resolveInfo);
|
||||
Intent targetIntent = new Intent(intent);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && app.wakeForDelivery) {
|
||||
try {
|
||||
if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) {
|
||||
int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid);
|
||||
logd("Adding app " + packageName + " for userId " + userId + " to the temp whitelist");
|
||||
addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push");
|
||||
List<ResolveInfo> infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER);
|
||||
if (infos == null || infos.isEmpty()) {
|
||||
logd("No target for message, wut?");
|
||||
} else {
|
||||
for (ResolveInfo resolveInfo : infos) {
|
||||
Intent targetIntent = new Intent(intent);
|
||||
targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
|
||||
if (resolveInfo.activityInfo.packageName.equals(packageName)) {
|
||||
// Wake up the package itself
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && app.wakeForDelivery) {
|
||||
try {
|
||||
if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) {
|
||||
int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid);
|
||||
logd("Adding app " + packageName + " for userId " + userId + " to the temp whitelist");
|
||||
addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
// We don't need receiver permission for our own package
|
||||
logd("Deliver message to own receiver " + resolveInfo);
|
||||
sendOrderedBroadcast(targetIntent, null);
|
||||
} else if (resolveInfo.filter.hasCategory(packageName)) {
|
||||
// Permission required
|
||||
logd("Deliver message to third-party receiver (with permission check)" + resolveInfo);
|
||||
sendOrderedBroadcast(targetIntent, receiverPermission);
|
||||
}
|
||||
}
|
||||
targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
|
||||
if (resolveInfo.activityInfo.packageName.equals(packageName)) {
|
||||
sendOrderedBroadcast(targetIntent, null);
|
||||
} else if (receiverPermission != null) {
|
||||
sendOrderedBroadcast(targetIntent, receiverPermission);
|
||||
} else {
|
||||
Log.w(TAG, resolveInfo.activityInfo.packageName + "/" + resolveInfo.activityInfo.name + " matches for C2D message to " + packageName + " but corresponding permission was not declared");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -569,6 +602,7 @@ public class McsService extends Service implements Handler.Callback {
|
||||
for (AppData appData : msg.app_data) {
|
||||
if (IDLE_NOTIFICATION.equals(appData.key)) {
|
||||
DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder()
|
||||
.id(Integer.toHexString(nextMessageId.incrementAndGet()))
|
||||
.from(FROM_FIELD)
|
||||
.sent(System.currentTimeMillis() / 1000)
|
||||
.ttl(0)
|
||||
@ -633,6 +667,22 @@ public class McsService extends Service implements Handler.Callback {
|
||||
scheduleReconnect(this);
|
||||
}
|
||||
return true;
|
||||
case MSG_ACK:
|
||||
logd("Ack initiated, reason: " + msg.obj);
|
||||
if (isConnected()) {
|
||||
IqStanza.Builder iq = new IqStanza.Builder()
|
||||
.type(IqStanza.IqType.SET)
|
||||
.id("")
|
||||
.extension(new Extension.Builder().id(13).data(ByteString.EMPTY).build()) // StreamAck
|
||||
.status(0L);
|
||||
if (inputStream.newStreamIdAvailable()) {
|
||||
iq.last_stream_id_received(inputStream.getStreamId());
|
||||
}
|
||||
send(MCS_IQ_STANZA_TAG, iq.build());
|
||||
} else {
|
||||
logd("Ignoring ack, not connected!");
|
||||
}
|
||||
return true;
|
||||
case MSG_OUTPUT_READY:
|
||||
logd("Sending login request...");
|
||||
send(MCS_LOGIN_REQUEST_TAG, buildLoginRequest());
|
||||
|
@ -28,13 +28,17 @@ import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.ForegroundServiceContext;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.common.Utils;
|
||||
|
||||
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION;
|
||||
import static org.microg.gms.gcm.GcmConstants.ERROR_SERVICE_NOT_AVAILABLE;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE;
|
||||
import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_ACK;
|
||||
import static org.microg.gms.gcm.McsConstants.ACTION_SEND;
|
||||
|
||||
class PushRegisterHandler extends Handler {
|
||||
private static final String TAG = "GmsGcmRegisterHdl";
|
||||
@ -54,40 +58,54 @@ class PushRegisterHandler extends Handler {
|
||||
return super.sendMessageAtTime(msg, uptimeMillis);
|
||||
}
|
||||
|
||||
private void sendReply(int what, int id, Messenger replyTo, Bundle data) {
|
||||
if (what == 0) {
|
||||
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
|
||||
outIntent.putExtras(data);
|
||||
Message message = Message.obtain();
|
||||
message.obj = outIntent;
|
||||
try {
|
||||
replyTo.send(message);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
} else {
|
||||
Bundle messageData = new Bundle();
|
||||
messageData.putBundle("data", data);
|
||||
Message response = Message.obtain();
|
||||
response.what = what;
|
||||
response.arg1 = id;
|
||||
response.setData(messageData);
|
||||
try {
|
||||
replyTo.send(response);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
private void sendReplyViaMessage(int what, int id, Messenger replyTo, Bundle messageData) {
|
||||
Message response = Message.obtain();
|
||||
response.what = what;
|
||||
response.arg1 = id;
|
||||
response.setData(messageData);
|
||||
try {
|
||||
replyTo.send(response);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void replyError(int what, int id, Messenger replyTo, String errorMessage) {
|
||||
private void sendReplyViaIntent(Intent outIntent, Messenger replyTo) {
|
||||
Message message = Message.obtain();
|
||||
message.obj = outIntent;
|
||||
try {
|
||||
replyTo.send(message);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendReply(int what, int id, Messenger replyTo, Bundle data, boolean oneWay) {
|
||||
if (what == 0) {
|
||||
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
|
||||
outIntent.putExtras(data);
|
||||
sendReplyViaIntent(outIntent, replyTo);
|
||||
return;
|
||||
}
|
||||
Bundle messageData = new Bundle();
|
||||
messageData.putBundle("data", data);
|
||||
sendReplyViaMessage(what, id, replyTo, messageData);
|
||||
}
|
||||
|
||||
private void replyError(int what, int id, Messenger replyTo, String errorMessage, boolean oneWay) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_ERROR, errorMessage);
|
||||
sendReply(what, id, replyTo, bundle);
|
||||
sendReply(what, id, replyTo, bundle, oneWay);
|
||||
}
|
||||
|
||||
private void replyNotAvailable(int what, int id, Messenger replyTo) {
|
||||
replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE);
|
||||
replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE, false);
|
||||
}
|
||||
|
||||
private PendingIntent getSelfAuthIntent() {
|
||||
Intent intent = new Intent();
|
||||
intent.setPackage("com.google.example.invalidpackage");
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -119,15 +137,9 @@ class PushRegisterHandler extends Handler {
|
||||
return;
|
||||
}
|
||||
Bundle data = msg.getData();
|
||||
if (data.getBoolean("oneWay", false)) {
|
||||
Log.w(TAG, "oneWay requested");
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = data.getString("pkg");
|
||||
Bundle subdata = data.getBundle("data");
|
||||
String sender = subdata.getString("sender");
|
||||
boolean delete = subdata.get("delete") != null;
|
||||
|
||||
try {
|
||||
PackageUtils.checkPackageUid(context, packageName, callingUid);
|
||||
@ -136,16 +148,46 @@ class PushRegisterHandler extends Handler {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: We should checkin and/or ask for permission here.
|
||||
Log.d(TAG, "handleMessage: package=" + packageName + " what=" + what + " id=" + id);
|
||||
|
||||
PushRegisterManager.completeRegisterRequest(context, database,
|
||||
new RegisterRequest()
|
||||
.build(Utils.getBuild(context))
|
||||
.sender(sender)
|
||||
.checkin(LastCheckinInfo.read(context))
|
||||
.app(packageName)
|
||||
.delete(delete)
|
||||
.extraParams(subdata),
|
||||
bundle -> sendReply(what, id, replyTo, bundle));
|
||||
boolean oneWay = data.getBoolean("oneWay", false);
|
||||
|
||||
switch (what) {
|
||||
case 0:
|
||||
case 1:
|
||||
// TODO: We should checkin and/or ask for permission here.
|
||||
String sender = subdata.getString("sender");
|
||||
boolean delete = subdata.get("delete") != null;
|
||||
|
||||
PushRegisterManager.completeRegisterRequest(context, database,
|
||||
new RegisterRequest()
|
||||
.build(Utils.getBuild(context))
|
||||
.sender(sender)
|
||||
.checkin(LastCheckinInfo.read(context))
|
||||
.app(packageName)
|
||||
.delete(delete)
|
||||
.extraParams(subdata),
|
||||
bundle -> sendReply(what, id, replyTo, bundle, oneWay));
|
||||
break;
|
||||
case 2:
|
||||
String messageId = subdata.getString("google.message_id");
|
||||
Log.d(TAG, "Ack " + messageId + " for " + packageName);
|
||||
Intent i = new Intent(context, McsService.class);
|
||||
i.setAction(ACTION_ACK);
|
||||
i.putExtra(EXTRA_APP, getSelfAuthIntent());
|
||||
new ForegroundServiceContext(context).startService(i);
|
||||
break;
|
||||
default:
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean("unsupported", true);
|
||||
sendReplyViaMessage(what, id, replyTo, bundle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (oneWay) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean("ack", true);
|
||||
sendReplyViaMessage(what, id, replyTo, bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user