mirror of
https://github.com/YTVanced/VancedMicroG
synced 2024-11-27 13:43:00 +00:00
Ensure checkin before gcm registration, fix gcm permission requests
This commit is contained in:
parent
b0e52b7a89
commit
26f2e859b8
10 changed files with 426 additions and 518 deletions
|
@ -198,8 +198,6 @@
|
|||
<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" />
|
||||
|
||||
|
@ -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" />
|
||||
|
||||
<activity
|
||||
android:name="org.microg.gms.ui.AboutFragment$AsActivity"
|
||||
|
|
|
@ -18,14 +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.preference.PreferenceManager;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver;
|
||||
|
@ -39,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
|
||||
|
@ -73,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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -50,7 +50,6 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
try {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/perm_desc_root"
|
||||
|
@ -57,8 +57,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>
|
||||
|
@ -70,61 +70,28 @@
|
|||
<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">
|
||||
|
||||
<android.support.v7.widget.ButtonBarLayout
|
||||
android:id="@+id/button_group"
|
||||
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
|
||||
android:id="@+id/current_page_text"
|
||||
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>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:id="@*android:id/spacer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="invisible">
|
||||
</android.support.v4.widget.Space>
|
||||
|
||||
<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>
|
||||
|
||||
</android.support.v7.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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue