From 26f2e859b8d424f20f42f3c2ec3736817f39b6e2 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 15 Oct 2020 22:08:05 +0200 Subject: [PATCH] Ensure checkin before gcm registration, fix gcm permission requests --- .../src/main/AndroidManifest.xml | 4 +- .../microg/gms/checkin/CheckinService.java | 16 +- .../microg/gms/checkin/TriggerReceiver.java | 6 +- .../microg/gms/gcm/PushRegisterHandler.java | 193 ---------- .../microg/gms/gcm/PushRegisterReceiver.java | 41 -- .../microg/gms/gcm/PushRegisterService.java | 200 ---------- .../org/microg/gms/gcm/TriggerReceiver.java | 1 - .../org/microg/gms/ui/AskPushPermission.java | 41 +- .../org/microg/gms/gcm/PushRegisterService.kt | 363 ++++++++++++++++++ .../src/main/res/layout/ask_gcm.xml | 79 ++-- 10 files changed, 426 insertions(+), 518 deletions(-) delete mode 100644 play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterReceiver.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 4265089e..572697b7 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -198,8 +198,6 @@ - - @@ -478,7 +476,7 @@ android:name="org.microg.gms.ui.AskPushPermission" android:excludeFromRecents="true" android:process=":ui" - android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert" /> + android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar" /> System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL) { + if (LastCheckinInfo.read(context).lastCheckin > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { CheckinService.schedule(context); return; } + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected() || force) { Intent subIntent = new Intent(context, CheckinService.class); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java deleted file mode 100644 index 15aaecc1..00000000 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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.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"; - - 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 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 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, oneWay); - } - - private void replyNotAvailable(int what, int id, Messenger replyTo) { - 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 - 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(); - - String packageName = data.getString("pkg"); - Bundle subdata = data.getBundle("data"); - - try { - PackageUtils.checkPackageUid(context, packageName, callingUid); - } catch (SecurityException e) { - Log.w(TAG, e); - return; - } - - Log.d(TAG, "handleMessage: package=" + packageName + " what=" + what + " id=" + id); - - 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); - } - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterReceiver.java deleted file mode 100644 index 64a40c83..00000000 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.content.Intent; - -import androidx.legacy.content.WakefulBroadcastReceiver; - -import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER; -import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_UNREGISTER; - -public class PushRegisterReceiver extends WakefulBroadcastReceiver { - private static final String TAG = "GmsGcmRegisterRcv"; - - @Override - public void onReceive(Context context, Intent intent) { - Intent intent2 = new Intent(context, PushRegisterService.class); - if (intent.getExtras().get("delete") != null) { - intent2.setAction(ACTION_C2DM_UNREGISTER); - } else { - intent2.setAction(ACTION_C2DM_REGISTER); - } - intent2.putExtras(intent.getExtras()); - startWakefulService(context, intent2); - } -} 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 deleted file mode 100644 index 96024409..00000000 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterService.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2013-2017 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.IntentService; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.legacy.content.WakefulBroadcastReceiver; - -import org.microg.gms.checkin.CheckinService; -import org.microg.gms.checkin.LastCheckinInfo; -import org.microg.gms.common.PackageUtils; -import org.microg.gms.common.Utils; -import org.microg.gms.ui.AskPushPermission; - -import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER; -import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION; -import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_UNREGISTER; -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_DELETE; -import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; -import static org.microg.gms.gcm.GcmConstants.EXTRA_KID; -import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER; -import static org.microg.gms.gcm.GcmConstants.EXTRA_PENDING_INTENT; -import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER; - -public class PushRegisterService extends IntentService { - private static final String TAG = "GmsGcmRegisterSvc"; - private static final String EXTRA_SKIP_TRY_CHECKIN = "skip_checkin"; - - private GcmDatabase database; - private static boolean requestPending = false; - - public PushRegisterService() { - super(TAG); - setIntentRedelivery(false); - } - - @Override - public void onCreate() { - super.onCreate(); - database = new GcmDatabase(this); - } - - @Override - public void onDestroy() { - super.onDestroy(); - database.close(); - } - - @Override - protected void onHandleIntent(Intent intent) { - WakefulBroadcastReceiver.completeWakefulIntent(intent); - Log.d(TAG, "onHandleIntent: " + intent); - - String requestId = null; - if (intent.hasExtra(EXTRA_KID) && intent.getStringExtra(EXTRA_KID).startsWith("|")) { - String[] kid = intent.getStringExtra(EXTRA_KID).split("\\|"); - if (kid.length >= 3 && "ID".equals(kid[1])) { - requestId = kid[2]; - } - } - - if (LastCheckinInfo.read(this).lastCheckin > 0) { - try { - if (ACTION_C2DM_UNREGISTER.equals(intent.getAction()) || - (ACTION_C2DM_REGISTER.equals(intent.getAction()) && "1".equals(intent.getStringExtra(EXTRA_DELETE)))) { - unregister(intent, requestId); - } else if (ACTION_C2DM_REGISTER.equals(intent.getAction())) { - register(intent, requestId); - } - } catch (Exception e) { - Log.w(TAG, e); - } - } else if (!intent.getBooleanExtra(EXTRA_SKIP_TRY_CHECKIN, false)) { - Log.d(TAG, "No checkin yet, trying to checkin"); - intent.putExtra(EXTRA_SKIP_TRY_CHECKIN, true); - Intent subIntent = new Intent(this, CheckinService.class); - subIntent.putExtra(CheckinService.EXTRA_FORCE_CHECKIN, true); - subIntent.putExtra(CheckinService.EXTRA_CALLBACK_INTENT, intent); - startService(subIntent); - } - } - - private void register(final Intent intent, String requestId) { - PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_APP); - final String packageName = PackageUtils.packageFromPendingIntent(pendingIntent); - - GcmDatabase.App app = database.getApp(packageName); - if (app == null && GcmPrefs.get(this).isConfirmNewApps()) { - try { - getPackageManager().getApplicationInfo(packageName, 0); // Check package exists - Intent i = new Intent(this, AskPushPermission.class); - i.putExtra(EXTRA_PENDING_INTENT, intent); - i.putExtra(EXTRA_APP, packageName); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(i); - } catch (PackageManager.NameNotFoundException e) { - replyNotAvailable(this, intent, packageName, requestId); - } - } else { - registerAndReply(this, database, intent, packageName, requestId); - } - } - - public static void replyNotAvailable(Context context, Intent intent, String packageName, String requestId) { - Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - outIntent.putExtra(EXTRA_ERROR, PushRegisterManager.attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId)); - sendReply(context, intent, packageName, outIntent); - } - - public static void registerAndReply(Context context, GcmDatabase database, Intent intent, String packageName, String requestId) { - Log.d(TAG, "register[req]: " + intent.toString() + " extras=" + intent.getExtras()); - PushRegisterManager.completeRegisterRequest(context, database, - new RegisterRequest() - .build(Utils.getBuild(context)) - .sender(intent.getStringExtra(EXTRA_SENDER)) - .checkin(LastCheckinInfo.read(context)) - .app(packageName) - .extraParams(intent.getExtras()), - bundle -> { - Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - outIntent.putExtras(bundle); - Log.d(TAG, "register[res]: " + outIntent.toString() + " extras=" + outIntent.getExtras()); - sendReply(context, intent, packageName, outIntent); - }); - } - - private static void sendReply(Context context, Intent intent, String packageName, Intent outIntent) { - try { - if (intent != null && intent.hasExtra(EXTRA_MESSENGER)) { - Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); - Message message = Message.obtain(); - message.obj = outIntent; - messenger.send(message); - return; - } - } catch (Exception e) { - Log.w(TAG, e); - } - - outIntent.setPackage(packageName); - context.sendOrderedBroadcast(outIntent, null); - } - - private void unregister(Intent intent, String requestId) { - PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_APP); - String packageName = PackageUtils.packageFromPendingIntent(pendingIntent); - Log.d(TAG, "unregister[req]: " + intent.toString() + " extras=" + intent.getExtras()); - - PushRegisterManager.completeRegisterRequest(this, database, - new RegisterRequest() - .build(Utils.getBuild(this)) - .sender(intent.getStringExtra(EXTRA_SENDER)) - .checkin(LastCheckinInfo.read(this)) - .app(packageName) - .extraParams(intent.getExtras()), - bundle -> { - Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - outIntent.putExtras(bundle); - Log.d(TAG, "unregister[res]: " + outIntent.toString() + " extras=" + outIntent.getExtras()); - sendReply(this, intent, packageName, outIntent); - }); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - Log.d(TAG, "onBind: " + intent.toString()); - if (ACTION_C2DM_REGISTER.equals(intent.getAction())) { - Messenger messenger = new Messenger(new PushRegisterHandler(this, database)); - return messenger.getBinder(); - } - return super.onBind(intent); - } - -} 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 9b16fe17..58cbd510 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 @@ -50,7 +50,6 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { } } - @Override public void onReceive(Context context, Intent intent) { try { diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java b/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java index 17dcf631..61a77f3e 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/AskPushPermission.java @@ -1,9 +1,11 @@ package org.microg.gms.ui; +import android.app.Activity; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.ResultReceiver; import android.text.Html; import android.view.View; import android.widget.TextView; @@ -20,13 +22,15 @@ import static org.microg.gms.gcm.GcmConstants.EXTRA_KID; import static org.microg.gms.gcm.GcmConstants.EXTRA_PENDING_INTENT; public class AskPushPermission extends FragmentActivity { + public static final String EXTRA_REQUESTED_PACKAGE = "package"; + public static final String EXTRA_RESULT_RECEIVER = "receiver"; + public static final String EXTRA_EXPLICIT = "explicit"; private GcmDatabase database; private String packageName; - private Intent intent; + private ResultReceiver resultReceiver; private boolean answered; - private String requestId; @Override public void onCreate(Bundle savedInstanceState) { @@ -34,18 +38,17 @@ public class AskPushPermission extends FragmentActivity { database = new GcmDatabase(this); - packageName = getIntent().getStringExtra(EXTRA_APP); - intent = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT); - - requestId = null; - if (intent.hasExtra(EXTRA_KID) && intent.getStringExtra(EXTRA_KID).startsWith("|")) { - String[] kid = intent.getStringExtra(EXTRA_KID).split("\\|"); - if (kid.length >= 3 && "ID".equals(kid[1])) { - requestId = kid[2]; - } + packageName = getIntent().getStringExtra(EXTRA_REQUESTED_PACKAGE); + resultReceiver = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER); + if (packageName == null || resultReceiver == null) { + answered = true; + finish(); + return; } if (database.getApp(packageName) != null) { + resultReceiver.send(Activity.RESULT_OK, Bundle.EMPTY); + answered = true; finish(); return; } @@ -64,12 +67,9 @@ public class AskPushPermission extends FragmentActivity { if (answered) return; database.noteAppKnown(packageName, true); answered = true; - new Thread(new Runnable() { - @Override - public void run() { - PushRegisterService.registerAndReply(AskPushPermission.this, database, intent, packageName, requestId); - } - }).start(); + Bundle bundle = new Bundle(); + bundle.putBoolean(EXTRA_EXPLICIT, true); + resultReceiver.send(Activity.RESULT_OK, bundle); finish(); } }); @@ -79,7 +79,9 @@ public class AskPushPermission extends FragmentActivity { if (answered) return; database.noteAppKnown(packageName, false); answered = true; - PushRegisterService.replyNotAvailable(AskPushPermission.this, intent, packageName, requestId); + Bundle bundle = new Bundle(); + bundle.putBoolean(EXTRA_EXPLICIT, true); + resultReceiver.send(Activity.RESULT_CANCELED, bundle); finish(); } }); @@ -92,8 +94,7 @@ public class AskPushPermission extends FragmentActivity { protected void onDestroy() { super.onDestroy(); if (!answered) { - PushRegisterService.replyNotAvailable(AskPushPermission.this, intent, packageName, requestId); - answered = true; + resultReceiver.send(Activity.RESULT_CANCELED, Bundle.EMPTY); } database.close(); } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt new file mode 100644 index 00000000..bfe2d743 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -0,0 +1,363 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.gcm + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.* +import android.util.Log +import androidx.legacy.content.WakefulBroadcastReceiver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope +import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.CheckinService +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 org.microg.gms.gcm.GcmConstants.* +import org.microg.gms.ui.AskPushPermission +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +private const val TAG = "GmsGcmRegister" + +private suspend fun ensureCheckinIsUpToDate(context: Context) { + if (!CheckinPrefs.get(context).isEnabled) throw RuntimeException("Checkin disabled") + val lastCheckin = LastCheckinInfo.read(context).lastCheckin + if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { + val resultData: Bundle = suspendCoroutine { continuation -> + val intent = Intent(context, CheckinService::class.java) + val continued = AtomicBoolean(false) + intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { + override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { + if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY) + } + }) + ForegroundServiceContext(context).startService(intent) + Handler().postDelayed({ + if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY) + }, 10000L) + } + if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) { + throw RuntimeException("No checkin available") + } + } +} + +private suspend fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) { + if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled") + val app = database.getApp(packageName) + if (app == null && GcmPrefs.get(context).isConfirmNewApps) { + val accepted: Boolean = suspendCoroutine { continuation -> + val i = Intent(context, AskPushPermission::class.java) + i.putExtra(AskPushPermission.EXTRA_REQUESTED_PACKAGE, packageName) + i.putExtra(AskPushPermission.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { + override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { + continuation.resume(resultCode == Activity.RESULT_OK) + } + }) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + i.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) + context.startActivity(i) + } + if (!accepted) { + throw RuntimeException("Push permission not granted to app") + } + } else if (!app.allowRegister) { + throw RuntimeException("Push permission not granted to app") + } +} + +suspend fun completeRegisterRequest(context: Context, database: GcmDatabase, request: RegisterRequest, requestId: String? = null): Bundle = suspendCoroutine { continuation -> + PushRegisterManager.completeRegisterRequest(context, database, requestId, request) { continuation.resume(it) } +} + +private val Intent.requestId: String? + get() { + val kidString = getStringExtra(GcmConstants.EXTRA_KID) ?: return null + if (kidString.startsWith("|")) { + val kid = kidString.split("\\|".toRegex()).toTypedArray() + if (kid.size >= 3 && "ID" == kid[1]) { + return kid[2] + } + } + return null + } + +private val Intent.app: PendingIntent? + get() = getParcelableExtra(EXTRA_APP) + +private val Intent.appPackageName: String? + get() = PackageUtils.packageFromPendingIntent(app) + +class PushRegisterService : LifecycleService() { + private lateinit var database: GcmDatabase + override fun onCreate() { + super.onCreate() + database = GcmDatabase(this) + } + + override fun onDestroy() { + super.onDestroy() + database.close() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + WakefulBroadcastReceiver.completeWakefulIntent(intent) + Log.d(TAG, "onStartCommand: $intent") + lifecycleScope.launchWhenStarted { + if (intent == null) return@launchWhenStarted + handleIntent(intent) + } + return super.onStartCommand(intent, flags, startId) + } + + private suspend fun handleIntent(intent: Intent) { + try { + ensureCheckinIsUpToDate(this) + if (ACTION_C2DM_UNREGISTER == intent.action || ACTION_C2DM_REGISTER == intent.action && "1" == intent.getStringExtra(EXTRA_DELETE)) { + unregister(intent) + } else if (ACTION_C2DM_REGISTER == intent.action) { + register(intent) + } + } catch (e: Exception) { + Log.w(TAG, e) + replyNotAvailable(intent) + } + } + + private fun replyNotAvailable(intent: Intent) { + val outIntent = Intent(ACTION_C2DM_REGISTRATION) + outIntent.putExtra(EXTRA_ERROR, PushRegisterManager.attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, intent.requestId)) + sendReply(intent, intent.appPackageName, outIntent) + } + + private suspend fun register(intent: Intent) { + val packageName = intent.appPackageName ?: throw RuntimeException("No package provided") + ensureAppRegistrationAllowed(this, database, packageName) + Log.d(TAG, "register[req]: " + intent.toString() + " extras=" + intent!!.extras) + val bundle = completeRegisterRequest(this, database, + RegisterRequest() + .build(Utils.getBuild(this)) + .sender(intent.getStringExtra(EXTRA_SENDER)) + .checkin(LastCheckinInfo.read(this)) + .app(packageName) + .extraParams(intent.extras)) + + val outIntent = Intent(ACTION_C2DM_REGISTRATION) + outIntent.putExtras(bundle) + Log.d(TAG, "register[res]: " + outIntent.toString() + " extras=" + outIntent.extras) + sendReply(intent, packageName, outIntent) + } + + private suspend fun unregister(intent: Intent) { + val packageName = intent.appPackageName ?: throw RuntimeException("No package provided") + Log.d(TAG, "unregister[req]: " + intent.toString() + " extras=" + intent.extras) + val bundle = completeRegisterRequest(this, database, RegisterRequest() + .build(Utils.getBuild(this)) + .sender(intent.getStringExtra(EXTRA_SENDER)) + .checkin(LastCheckinInfo.read(this)) + .app(packageName) + .extraParams(intent.extras) + ) + val outIntent = Intent(ACTION_C2DM_REGISTRATION) + outIntent.putExtras(bundle) + Log.d(TAG, "unregister[res]: " + outIntent.toString() + " extras=" + outIntent.extras) + sendReply(intent, packageName, outIntent) + } + + private fun sendReply(intent: Intent, packageName: String?, outIntent: Intent) { + if (sendReplyToMessenger(intent, outIntent)) return + outIntent.setPackage(packageName) + sendOrderedBroadcast(outIntent, null) + } + + private fun sendReplyToMessenger(intent: Intent, outIntent: Intent): Boolean { + try { + val messenger = intent.getParcelableExtra(EXTRA_MESSENGER) ?: return false + val message = Message.obtain() + message.obj = outIntent + messenger.send(message) + return true + } catch (e: Exception) { + Log.w(TAG, e) + return false + } + } + + override fun onBind(intent: Intent): IBinder? { + Log.d(TAG, "onBind: $intent") + super.onBind(intent) + if (ACTION_C2DM_REGISTER == intent.action) { + val messenger = Messenger(PushRegisterHandler(this, database, lifecycle)) + return messenger.binder + } + return null + } +} + +internal class PushRegisterHandler(private val context: Context, private val database: GcmDatabase, private val lifecycle: Lifecycle) : Handler(), LifecycleOwner { + override fun getLifecycle(): Lifecycle = lifecycle + + private var callingUid = 0 + override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean { + callingUid = Binder.getCallingUid() + return super.sendMessageAtTime(msg, uptimeMillis) + } + + private fun sendReplyViaMessage(what: Int, id: Int, replyTo: Messenger, messageData: Bundle) { + val response = Message.obtain() + response.what = what + response.arg1 = id + response.data = messageData + try { + replyTo.send(response) + } catch (e: RemoteException) { + Log.w(TAG, e) + } + } + + private fun sendReplyViaIntent(outIntent: Intent, replyTo: Messenger) { + val message = Message.obtain() + message.obj = outIntent + try { + replyTo.send(message) + } catch (e: RemoteException) { + Log.w(TAG, e) + } + } + + private fun sendReply(what: Int, id: Int, replyTo: Messenger, data: Bundle, oneWay: Boolean) { + if (what == 0) { + val outIntent = Intent(ACTION_C2DM_REGISTRATION) + outIntent.putExtras(data) + sendReplyViaIntent(outIntent, replyTo) + return + } + val messageData = Bundle() + messageData.putBundle("data", data) + sendReplyViaMessage(what, id, replyTo, messageData) + } + + private fun replyError(what: Int, id: Int, replyTo: Messenger, errorMessage: String, oneWay: Boolean) { + val bundle = Bundle() + bundle.putString(EXTRA_ERROR, errorMessage) + sendReply(what, id, replyTo, bundle, oneWay) + } + + private fun replyNotAvailable(what: Int, id: Int, replyTo: Messenger) { + replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE, false) + } + + private val selfAuthIntent: PendingIntent + private get() { + val intent = Intent() + intent.setPackage("com.google.example.invalidpackage") + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + + override fun handleMessage(msg: Message) { + var msg = msg + val obj = msg.obj + if (msg.what == 0) { + if (obj is Intent) { + val nuMsg = Message.obtain() + nuMsg.what = msg.what + nuMsg.arg1 = 0 + nuMsg.replyTo = null + val packageName = obj.appPackageName + val data = Bundle() + data.putBoolean("oneWay", false) + data.putString("pkg", packageName) + data.putBundle("data", msg.data) + nuMsg.data = data + msg = nuMsg + } else { + return + } + } + val what = msg.what + val id = msg.arg1 + val replyTo = msg.replyTo + if (replyTo == null) { + Log.w(TAG, "replyTo is null") + return + } + val data = msg.data + val packageName = data.getString("pkg") ?: return + val subdata = data.getBundle("data") + try { + PackageUtils.checkPackageUid(context, packageName, callingUid) + } catch (e: SecurityException) { + Log.w(TAG, e) + return + } + Log.d(TAG, "handleMessage: package=$packageName what=$what id=$id") + val oneWay = data.getBoolean("oneWay", false) + when (what) { + 0, 1 -> { + lifecycleScope.launchWhenStarted { + try { + val sender = subdata?.getString("sender") + val delete = subdata?.get("delete") != null + ensureCheckinIsUpToDate(context) + if (!delete) ensureAppRegistrationAllowed(context, database, packageName) + val bundle = completeRegisterRequest(context, database, + RegisterRequest() + .build(Utils.getBuild(context)) + .sender(sender) + .checkin(LastCheckinInfo.read(context)) + .app(packageName) + .delete(delete) + .extraParams(subdata)) + sendReply(what, id, replyTo, bundle, oneWay) + } catch (e: Exception) { + Log.w(TAG, e) + replyNotAvailable(what, id, replyTo) + } + } + } + 2 -> { + val messageId = subdata!!.getString("google.message_id") + Log.d(TAG, "Ack $messageId for $packageName") + val i = Intent(context, McsService::class.java) + i.action = McsConstants.ACTION_ACK + i.putExtra(EXTRA_APP, selfAuthIntent) + ForegroundServiceContext(context).startService(i) + } + else -> { + val bundle = Bundle() + bundle.putBoolean("unsupported", true) + sendReplyViaMessage(what, id, replyTo, bundle) + return + } + } + if (oneWay) { + val bundle = Bundle() + bundle.putBoolean("ack", true) + sendReplyViaMessage(what, id, replyTo, bundle) + } + } +} + +class PushRegisterReceiver : WakefulBroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val intent2 = Intent(context, PushRegisterService::class.java) + if (intent.extras!!["delete"] != null) { + intent2.action = ACTION_C2DM_UNREGISTER + } else { + intent2.action = ACTION_C2DM_REGISTER + } + intent2.putExtras(intent.extras!!) + startWakefulService(context, intent2) + } +} diff --git a/play-services-core/src/main/res/layout/ask_gcm.xml b/play-services-core/src/main/res/layout/ask_gcm.xml index a5c20212..903bfd9c 100644 --- a/play-services-core/src/main/res/layout/ask_gcm.xml +++ b/play-services-core/src/main/res/layout/ask_gcm.xml @@ -31,11 +31,11 @@ android:id="@+id/desc_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingEnd="16dip" - android:paddingLeft="20dip" - android:paddingRight="16dip" android:paddingStart="20dip" - android:paddingTop="18dip"> + android:paddingLeft="20dip" + android:paddingTop="18dip" + android:paddingEnd="16dip" + android:paddingRight="16dip"> @@ -70,61 +70,28 @@ + android:paddingEnd="16dip" + android:paddingRight="16dip"> - + android:text="@string/deny"> + - - - - - - - - - - - +