Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 719cd51d authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

Push Messaging: Handle more edge cases, fix some apps not showing up as registered

parent e4710185
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -205,16 +205,18 @@
            android:permission="com.google.android.c2dm.permission.RECEIVE">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.REGISTER"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.UNREGISTER"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

        <receiver android:name="org.microg.gms.gcm.PushRegisterReceiver">
            <intent-filter>
                <action android:name="com.google.iid.TOKEN_REQUEST"/>
            </intent-filter>
        </receiver>

        <service android:name="org.microg.gms.gcm.McsService"/>

        <receiver
+7 −1
Original line number Diff line number Diff line
@@ -81,7 +81,13 @@ public class HttpFormClient {
        os.close();

        if (connection.getResponseCode() != 200) {
            throw new IOException(connection.getResponseMessage());
            String error = connection.getResponseMessage();
            try {
                error = new String(Utils.readStreamToEnd(connection.getErrorStream()));
            } catch (IOException e) {
                // Ignore
            }
            throw new IOException(error);
        }

        String result = new String(Utils.readStreamToEnd(connection.getInputStream()));
+8 −0
Original line number Diff line number Diff line
@@ -155,4 +155,12 @@ public class PackageUtils {
            return -1;
        }
    }

    public static String versionName(Context context, String packageName) {
        try {
            return context.getPackageManager().getPackageInfo(packageName, 0).versionName;
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }
}
+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 microG Project Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.microg.gms.gcm;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

import org.microg.gms.checkin.LastCheckinInfo;
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_ERROR;

class PushRegisterHandler extends Handler {
    private static final String TAG = "GmsGcmRegisterHdl";

    private Context context;
    private int callingUid;
    private GcmDatabase database;

    public PushRegisterHandler(Context context, GcmDatabase database) {
        this.context = context;
        this.database = database;
    }

    @Override
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        this.callingUid = Binder.getCallingUid();
        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 replyError(int what, int id, Messenger replyTo, String errorMessage) {
        Bundle bundle = new Bundle();
        bundle.putString(EXTRA_ERROR, errorMessage);
        sendReply(what, id, replyTo, bundle);
    }

    private void replyNotAvailable(int what, int id, Messenger replyTo) {
        replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE);
    }

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == 0) {
            if (msg.obj instanceof Intent) {
                Message nuMsg = Message.obtain();
                nuMsg.what = msg.what;
                nuMsg.arg1 = 0;
                nuMsg.replyTo = null;
                PendingIntent pendingIntent = ((Intent) msg.obj).getParcelableExtra(EXTRA_APP);
                String packageName = PackageUtils.packageFromPendingIntent(pendingIntent);
                Bundle data = new Bundle();
                data.putBoolean("oneWay", false);
                data.putString("pkg", packageName);
                data.putBundle("data", msg.getData());
                nuMsg.setData(data);
                msg = nuMsg;
            } else {
                return;
            }
        }

        int what = msg.what;
        int id = msg.arg1;
        Messenger replyTo = msg.replyTo;
        if (replyTo == null) {
            Log.w(TAG, "replyTo is null");
            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);
        } catch (SecurityException e) {
            Log.w(TAG, e);
            return;
        }

        // TODO: We should checkin and/or ask for permission here.

        PushRegisterManager.completeRegisterRequest(context, database,
                new RegisterRequest()
                        .build(Utils.getBuild(context))
                        .sender(sender)
                        .checkin(LastCheckinInfo.read(context))
                        .app(packageName)
                        .delete(delete)
                        .appid(subdata.getString("appid"), subdata.getString("gmp_app_id")),
                bundle -> sendReply(what, id, replyTo, bundle));
    }
}
+168 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 microG Project Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.microg.gms.gcm;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;

import org.microg.gms.checkin.LastCheckinInfo;
import org.microg.gms.common.HttpFormClient;
import org.microg.gms.common.PackageUtils;
import org.microg.gms.common.Utils;

import java.io.IOException;

import static org.microg.gms.gcm.GcmConstants.ERROR_SERVICE_NOT_AVAILABLE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR;
import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_RETRY_AFTER;
import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED;

public class PushRegisterManager {
    private static final String TAG = "GmsGcmRegisterMgr";

    public static RegisterResponse unregister(Context context, String packageName, String pkgSignature, String sender, String info) {
        GcmDatabase database = new GcmDatabase(context);
        RegisterResponse response = new RegisterResponse();
        try {
            response = new RegisterRequest()
                    .build(Utils.getBuild(context))
                    .sender(sender)
                    .info(info)
                    .checkin(LastCheckinInfo.read(context))
                    .app(packageName, pkgSignature)
                    .delete(true)
                    .getResponse();
        } catch (IOException e) {
            Log.w(TAG, e);
        }
        if (!packageName.equals(response.deleted)) {
            database.noteAppRegistrationError(packageName, response.responseText);
        } else {
            database.noteAppUnregistered(packageName, pkgSignature);
        }
        database.close();
        return response;
    }

    public interface BundleCallback {
        void onResult(Bundle bundle);
    }

    public static void completeRegisterRequest(Context context, GcmDatabase database, RegisterRequest request, BundleCallback callback) {
        completeRegisterRequest(context, database, null, request, callback);
    }

    public static void completeRegisterRequest(Context context, GcmDatabase database, String requestId, RegisterRequest request, BundleCallback callback) {
        if (request.app != null) {
            if (request.appSignature == null)
                request.appSignature = PackageUtils.firstSignatureDigest(context, request.app);
            if (request.appVersion <= 0)
                request.appVersion = PackageUtils.versionCode(context, request.app);
            if (request.appVersionName == null)
                request.appVersionName = PackageUtils.versionName(context, request.app);
        }

        GcmDatabase.App app = database.getApp(request.app);
        GcmPrefs prefs = GcmPrefs.get(context);
        if (!request.delete) {
            if (!prefs.isEnabled() ||
                    (app != null && !app.allowRegister) ||
                    LastCheckinInfo.read(context).lastCheckin <= 0 ||
                    (app == null && prefs.isConfirmNewApps())) {
                Bundle bundle = new Bundle();
                bundle.putString(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE);
                callback.onResult(bundle);
                return;
            }
        } else {
            if (database.getRegistrationsByApp(request.app).isEmpty()) {
                Bundle bundle = new Bundle();
                bundle.putString(EXTRA_UNREGISTERED, attachRequestId(request.app, requestId));
                callback.onResult(bundle);
                return;
            }
        }

        request.getResponseAsync(new HttpFormClient.Callback<RegisterResponse>() {
            @Override
            public void onResponse(RegisterResponse response) {
                callback.onResult(handleResponse(database, request, response, requestId));
            }

            @Override
            public void onException(Exception e) {
                Log.w(TAG, e);
                callback.onResult(handleResponse(database, request, e, requestId));
            }
        });
    }



    private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, RegisterResponse response, String requestId) {
        return handleResponse(database, request, response, null, requestId);
    }

    private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, Exception e, String requestId) {
        return handleResponse(database, request, null, e, requestId);
    }

    private static Bundle handleResponse(GcmDatabase database, RegisterRequest request, RegisterResponse response, Exception e, String requestId) {
        Bundle resultBundle = new Bundle();
        if (response == null && e == null) {
            resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
        } else if (e != null) {
            if (e.getMessage() != null && e.getMessage().startsWith("Error=")) {
                String errorMessage = e.getMessage().substring(6);
                database.noteAppRegistrationError(request.app, errorMessage);
                resultBundle.putString(EXTRA_ERROR, attachRequestId(errorMessage, requestId));
            } else {
                resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
            }
        } else {
            if (!request.delete) {
                if (response.token == null) {
                    database.noteAppRegistrationError(request.app, response.responseText);
                    resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
                } else {
                    database.noteAppRegistered(request.app, request.appSignature, response.token);
                    resultBundle.putString(EXTRA_REGISTRATION_ID, attachRequestId(response.token, requestId));
                }
            } else {
                if (!request.app.equals(response.deleted) && !request.app.equals(response.token)) {
                    database.noteAppRegistrationError(request.app, response.responseText);
                    resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId));
                } else {
                    database.noteAppUnregistered(request.app, request.appSignature);
                    resultBundle.putString(EXTRA_UNREGISTERED, attachRequestId(request.app, requestId));
                }
            }

            if (response.retryAfter != null && !response.retryAfter.contains(":")) {
                resultBundle.putLong(EXTRA_RETRY_AFTER, Long.parseLong(response.retryAfter));
            }
        }
        return resultBundle;
    }

    public static String attachRequestId(String msg, String requestId) {
        if (requestId == null) return msg;
        return "|ID|" + requestId + "|" + msg;
    }
}
Loading