26f2e85 + Wrapper update

This commit is contained in:
Oizaro 2020-10-16 01:16:59 +02:00
parent 8d0586487e
commit cb719fce32
13 changed files with 447 additions and 524 deletions

View File

@ -35,7 +35,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:${androidBuildGradleVersion}"
classpath "com.android.tools.build:gradle:4.1.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.squareup.wire:wire-gradle-plugin:$wireVersion"
}

View File

@ -1,6 +1,6 @@
#Tue Sep 29 23:46:06 CEST 2020
#Fri Oct 16 00:14:59 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@ -118,9 +118,7 @@
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.AIRPLANE_MODE" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
<action android:name="android.server.checkin.CHECKIN" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
@ -314,7 +312,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" />
<activity
android:name="org.microg.gms.ui.AboutFragment$AsActivity"

View File

@ -18,13 +18,16 @@ package org.microg.gms.checkin;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
import androidx.legacy.content.WakefulBroadcastReceiver;
@ -38,11 +41,15 @@ import org.microg.gms.people.PeopleManager;
public class CheckinService extends IntentService {
private static final String TAG = "GmsCheckinSvc";
public static final long MAX_VALID_CHECKIN_AGE = 24 * 60 * 60 * 1000; // 12 hours
public static final long REGULAR_CHECKIN_INTERVAL = 12 * 60 * 60 * 1000; // 12 hours
public static final long BACKUP_CHECKIN_DELAY = 3 * 60 * 60 * 1000; // 3 hours
public static final String BIND_ACTION = "com.google.android.gms.checkin.BIND_TO_SERVICE";
public static final String EXTRA_FORCE_CHECKIN = "force";
@Deprecated
public static final String EXTRA_CALLBACK_INTENT = "callback";
public static final String EXTRA_RESULT_RECEIVER = "receiver";
public static final String EXTRA_NEW_CHECKIN_TIME = "checkin_time";
private ICheckinService iface = new ICheckinService.Stub() {
@Override
@ -72,6 +79,14 @@ public class CheckinService extends IntentService {
if (intent.hasExtra(EXTRA_CALLBACK_INTENT)) {
startService((Intent) intent.getParcelableExtra(EXTRA_CALLBACK_INTENT));
}
if (intent.hasExtra(EXTRA_RESULT_RECEIVER)) {
ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if (receiver != null) {
Bundle bundle = new Bundle();
bundle.putLong(EXTRA_NEW_CHECKIN_TIME, info.lastCheckin);
receiver.send(Activity.RESULT_OK, bundle);
}
}
}
}
} catch (Exception e) {

View File

@ -31,20 +31,20 @@ import static org.microg.gms.checkin.CheckinService.REGULAR_CHECKIN_INTERVAL;
public class TriggerReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GmsCheckinTrigger";
private static boolean registered = false;
@Override
public void onReceive(Context context, Intent intent) {
try {
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (CheckinPrefs.get(context).isEnabled() || force) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()) &&
LastCheckinInfo.read(context).lastCheckin > 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);

View File

@ -1,196 +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.Build;
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());
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
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);
}
}
}

View File

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

View File

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

View File

@ -50,7 +50,6 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
}
}
@Override
public void onReceive(Context context, Intent intent) {
try {

View File

@ -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.widget.TextView;
@ -19,13 +21,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) {
@ -33,18 +37,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;
}
@ -61,14 +64,18 @@ public class AskPushPermission extends FragmentActivity {
if (answered) return;
database.noteAppKnown(packageName, true);
answered = true;
new Thread(() -> 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();
});
findViewById(R.id.permission_deny_button).setOnClickListener(v -> {
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();
});
} catch (PackageManager.NameNotFoundException e) {
@ -80,8 +87,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();
}

View File

@ -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<Messenger>(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)
}
}

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 The Android Open Source Project
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.
@ -16,37 +13,40 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
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">
<LinearLayout
android:id="@+id/perm_desc_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/permission_icon"
android:layout_width="36dip"
android:layout_height="36dip"
android:scaleType="fitCenter"
android:src="@drawable/ic_cloud_bell"
app:tint="?attr/colorAccent">
android:tint="?attr/colorAccent">
</ImageView>
<TextView
@ -54,8 +54,8 @@
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dip"
android:paddingStart="16dip"
android:paddingLeft="16dip"
android:text="Allow {appName} to register for push notifications?"
android:textSize="20sp">
</TextView>
@ -67,62 +67,31 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="16dip"
android:gravity="end"
android:orientation="horizontal"
android:paddingStart="20dip"
android:paddingLeft="20dip"
android:paddingRight="16dip"
android:paddingStart="20dip">
android:paddingEnd="16dip"
android:paddingRight="16dip">
<androidx.appcompat.widget.ButtonBarLayout
android:layout_width="match_parent"
<Button
android:id="@+id/permission_deny_button"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingBottom="4dp"
android:paddingLeft="6dip"
android:paddingStart="6dip"
android:paddingTop="4dp">
android:text="@string/deny">
</Button>
<TextView
style="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:paddingEnd="12dp"
android:paddingRight="12dp"
android:textColor="?android:attr/textColorSecondary"
android:visibility="invisible">
</TextView>
<View
android:id="@*android:id/spacer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible">
</View>
<Button
android:id="@+id/permission_deny_button"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deny">
</Button>
<Button
android:id="@+id/permission_allow_button"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/allow">
</Button>
</androidx.appcompat.widget.ButtonBarLayout>
<Button
android:id="@+id/permission_allow_button"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/allow">
</Button>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@ -25,4 +25,14 @@
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
<style name="Theme.AppCompat.Light.Dialog.Alert.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>