Fix FCM registration for apps using firebase >= 20.1.1

With version 20.1.1 the Firebase Cloud Messaging SDK started to use
the Firebase Installations SDK, which affects the FCM registration
process.

The implementation of FCM registration in microG failed to pass extra
parameters that became relevant with the introduction of the
Firebase Installations SDK to the FCM registration endpoint.

These additional parameters are passed through to the endpoint with an 'X-' prefix.
This commit is contained in:
georgeto 2020-06-09 02:52:03 +02:00 committed by Marvin W
parent 8eff51cfb6
commit 5146559f89
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
6 changed files with 79 additions and 37 deletions

View File

@ -46,6 +46,13 @@ public class HttpFormClient {
try { try {
field.setAccessible(true); field.setAccessible(true);
Object objVal = field.get(request); Object objVal = field.get(request);
if (field.isAnnotationPresent(RequestContentDynamic.class)) {
Map<String, String> contentParams = (Map<String, String>) objVal;
for (Map.Entry<String, String> param : contentParams.entrySet()) {
appendParam(content, param.getKey(), param.getValue());
}
continue;
}
String value = objVal != null ? String.valueOf(objVal) : null; String value = objVal != null ? String.valueOf(objVal) : null;
Boolean boolVal = null; Boolean boolVal = null;
if (field.getType().equals(boolean.class)) { if (field.getType().equals(boolean.class)) {
@ -65,9 +72,7 @@ public class HttpFormClient {
value = valueFromBoolVal(value, boolVal, annotation.truePresent(), annotation.falsePresent()); value = valueFromBoolVal(value, boolVal, annotation.truePresent(), annotation.falsePresent());
if (value != null || annotation.nullPresent()) { if (value != null || annotation.nullPresent()) {
for (String key : annotation.value()) { for (String key : annotation.value()) {
if (content.length() > 0) appendParam(content, key, value);
content.append("&");
content.append(Uri.encode(key)).append("=").append(Uri.encode(String.valueOf(value)));
} }
} }
} }
@ -109,6 +114,12 @@ public class HttpFormClient {
} }
} }
private static void appendParam(StringBuilder content, String key, String value) {
if (content.length() > 0)
content.append("&");
content.append(Uri.encode(key)).append("=").append(Uri.encode(String.valueOf(value)));
}
private static <T> T parseResponse(Class<T> tClass, HttpURLConnection connection, String result) throws IOException { private static <T> T parseResponse(Class<T> tClass, HttpURLConnection connection, String result) throws IOException {
Map<String, List<String>> headerFields = connection.getHeaderFields(); Map<String, List<String>> headerFields = connection.getHeaderFields();
T response; T response;
@ -227,6 +238,11 @@ public class HttpFormClient {
public boolean nullPresent() default false; public boolean nullPresent() default false;
} }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RequestContentDynamic {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface ResponseField { public @interface ResponseField {

View File

@ -264,4 +264,12 @@ public class PackageUtils {
return null; return null;
} }
} }
public static int targetSdkVersion(Context context, String packageName) {
try {
return context.getPackageManager().getApplicationInfo(packageName, 0).targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
return -1;
}
}
} }

View File

@ -145,7 +145,7 @@ class PushRegisterHandler extends Handler {
.checkin(LastCheckinInfo.read(context)) .checkin(LastCheckinInfo.read(context))
.app(packageName) .app(packageName)
.delete(delete) .delete(delete)
.appid(subdata.getString("appid"), subdata.getString("gmp_app_id")), .extraParams(subdata),
bundle -> sendReply(what, id, replyTo, bundle)); bundle -> sendReply(what, id, replyTo, bundle));
} }
} }

View File

@ -70,12 +70,16 @@ public class PushRegisterManager {
public static void completeRegisterRequest(Context context, GcmDatabase database, String requestId, RegisterRequest request, BundleCallback callback) { public static void completeRegisterRequest(Context context, GcmDatabase database, String requestId, RegisterRequest request, BundleCallback callback) {
if (request.app != null) { if (request.app != null) {
if (request.appSignature == null)
request.appSignature = PackageUtils.firstSignatureDigest(context, request.app);
if (request.appVersion <= 0) if (request.appVersion <= 0)
request.appVersion = PackageUtils.versionCode(context, request.app); request.appVersion = PackageUtils.versionCode(context, request.app);
if (request.appVersionName == null) if (!request.delete) {
request.appVersionName = PackageUtils.versionName(context, request.app); if (request.appSignature == null) {
request.appSignature = PackageUtils.firstSignatureDigest(context, request.app);
}
request.sdkVersion = PackageUtils.targetSdkVersion(context, request.app);
if (!request.hasExtraParam(GcmConstants.EXTRA_APP_VERSION_NAME))
request.extraParam(GcmConstants.EXTRA_APP_VERSION_NAME, PackageUtils.versionName(context, request.app));
}
} }
GcmDatabase.App app = database.getApp(request.app); GcmDatabase.App app = database.getApp(request.app);

View File

@ -20,7 +20,6 @@ import android.app.IntentService;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.IBinder; import android.os.IBinder;
import android.os.Message; import android.os.Message;
@ -140,7 +139,8 @@ public class PushRegisterService extends IntentService {
.build(Utils.getBuild(context)) .build(Utils.getBuild(context))
.sender(intent.getStringExtra(EXTRA_SENDER)) .sender(intent.getStringExtra(EXTRA_SENDER))
.checkin(LastCheckinInfo.read(context)) .checkin(LastCheckinInfo.read(context))
.app(packageName), .app(packageName)
.extraParams(intent.getExtras()),
bundle -> { bundle -> {
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
outIntent.putExtras(bundle); outIntent.putExtras(bundle);
@ -176,7 +176,8 @@ public class PushRegisterService extends IntentService {
.build(Utils.getBuild(this)) .build(Utils.getBuild(this))
.sender(intent.getStringExtra(EXTRA_SENDER)) .sender(intent.getStringExtra(EXTRA_SENDER))
.checkin(LastCheckinInfo.read(this)) .checkin(LastCheckinInfo.read(this))
.app(packageName), .app(packageName)
.extraParams(intent.getExtras()),
bundle -> { bundle -> {
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
outIntent.putExtras(bundle); outIntent.putExtras(bundle);

View File

@ -16,19 +16,24 @@
package org.microg.gms.gcm; package org.microg.gms.gcm;
import android.os.Bundle;
import android.text.TextUtils;
import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.checkin.LastCheckinInfo;
import org.microg.gms.common.Build; import org.microg.gms.common.Build;
import org.microg.gms.common.Constants;
import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.HttpFormClient;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.microg.gms.common.HttpFormClient.RequestContent; import static org.microg.gms.common.HttpFormClient.RequestContent;
import static org.microg.gms.common.HttpFormClient.RequestContentDynamic;
import static org.microg.gms.common.HttpFormClient.RequestHeader; import static org.microg.gms.common.HttpFormClient.RequestHeader;
public class RegisterRequest extends HttpFormClient.Request { public class RegisterRequest extends HttpFormClient.Request {
private static final String SERVICE_URL = "https://android.clients.google.com/c2dm/register3"; private static final String SERVICE_URL = "https://android.clients.google.com/c2dm/register3";
private static final String USER_AGENT = "Android-GCM/1.3 (%s %s)"; private static final String USER_AGENT = "Android-GCM/1.5 (%s %s)";
@RequestHeader("Authorization") @RequestHeader("Authorization")
private String auth; private String auth;
@ -42,35 +47,26 @@ public class RegisterRequest extends HttpFormClient.Request {
public String appSignature; public String appSignature;
@RequestContent("app_ver") @RequestContent("app_ver")
public int appVersion; public int appVersion;
@RequestContent("app_ver_name")
public String appVersionName;
@RequestContent("info") @RequestContent("info")
public String info; public String info;
@RequestContent({"sender", "subtype"}) @RequestContent("sender")
public String sender; public String sender;
@RequestContent({"X-GOOG.USER_AID", "device"}) @RequestContent("device")
public long androidId; public long androidId;
@RequestContent("delete") @RequestContent("delete")
public boolean delete; public boolean delete;
public long securityToken; public long securityToken;
public String deviceName; public String deviceName;
public String buildVersion; public String buildVersion;
@RequestContent("osv") @RequestContent("target_ver")
public int sdkVersion; public Integer sdkVersion;
@RequestContent("gmsv") @RequestContentDynamic
public int gmsVersion; private Map<String, String> extraParams = new LinkedHashMap<>();
@RequestContent("scope")
public String scope = "*";
@RequestContent("appid")
public String appId;
@RequestContent("gmp_app_id")
public String gmpAppId;
@Override @Override
public void prepare() { public void prepare() {
userAgent = String.format(USER_AGENT, deviceName, buildVersion); userAgent = String.format(USER_AGENT, deviceName, buildVersion);
auth = "AidLogin " + androidId + ":" + securityToken; auth = "AidLogin " + androidId + ":" + securityToken;
gmsVersion = Constants.MAX_REFERENCE_VERSION;
} }
public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) { public RegisterRequest checkin(LastCheckinInfo lastCheckinInfo) {
@ -90,17 +86,10 @@ public class RegisterRequest extends HttpFormClient.Request {
return this; return this;
} }
public RegisterRequest app(String app, String appSignature, int appVersion, String appVersionName) { public RegisterRequest app(String app, String appSignature, int appVersion) {
this.app = app; this.app = app;
this.appSignature = appSignature; this.appSignature = appSignature;
this.appVersion = appVersion; this.appVersion = appVersion;
this.appVersionName = appVersionName;
return this;
}
public RegisterRequest appid(String appid, String gmpAppId) {
this.appId = appid;
this.gmpAppId = gmpAppId;
return this; return this;
} }
@ -117,7 +106,6 @@ public class RegisterRequest extends HttpFormClient.Request {
public RegisterRequest build(Build build) { public RegisterRequest build(Build build) {
deviceName = build.device; deviceName = build.device;
buildVersion = build.id; buildVersion = build.id;
sdkVersion = build.sdk;
return this; return this;
} }
@ -130,6 +118,31 @@ public class RegisterRequest extends HttpFormClient.Request {
return this; return this;
} }
public RegisterRequest extraParams(Bundle extraBundle) {
for (String key : extraBundle.keySet()) {
if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE)) {
extraParam(key, extraBundle.getString(key));
}
}
return this;
}
public RegisterRequest extraParam(String key, String value) {
// Ignore empty registration extras
if (!TextUtils.isEmpty(value)) {
extraParams.put(extraParamKey(key), value);
}
return this;
}
public boolean hasExtraParam(String key) {
return extraParams.containsKey(extraParamKey(key));
}
private static String extraParamKey(String key) {
return "X-" + key;
}
public RegisterResponse getResponse() throws IOException { public RegisterResponse getResponse() throws IOException {
return HttpFormClient.request(SERVICE_URL, this, RegisterResponse.class); return HttpFormClient.request(SERVICE_URL, this, RegisterResponse.class);
} }