Repo cloned
This commit is contained in:
commit
496ae75f58
7988 changed files with 1451097 additions and 0 deletions
26
core-gms/cloud-messaging/build.gradle.kts
Normal file
26
core-gms/cloud-messaging/build.gradle.kts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
plugins {
|
||||
id("signal-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.google.android.gms.cloudmessaging"
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigField("boolean", "NOTIFICATION_PAYLOAD_ENABLED", "false")
|
||||
}
|
||||
|
||||
lint {
|
||||
disable += setOf("LogNotSignal")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":core-gms:base"))
|
||||
api(project(":core-gms:safeparcel"))
|
||||
api(project(":core-gms:tasks"))
|
||||
annotationProcessor(project(":core-gms:safeparcel-processor"))
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArrayMap;
|
||||
|
||||
import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable;
|
||||
import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Class;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Map;
|
||||
|
||||
@Keep
|
||||
@Class(creator = "CloudMessageCreator")
|
||||
public final class CloudMessage extends AbstractSafeParcelable {
|
||||
|
||||
private static final String TAG = "CloudMessage";
|
||||
|
||||
public static final int PRIORITY_UNKNOWN = 0;
|
||||
public static final int PRIORITY_HIGH = 1;
|
||||
public static final int PRIORITY_NORMAL = 2;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {
|
||||
PRIORITY_UNKNOWN,
|
||||
PRIORITY_HIGH,
|
||||
PRIORITY_NORMAL,
|
||||
})
|
||||
public @interface MessagePriority {
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<CloudMessage> CREATOR = new CloudMessageCreator();
|
||||
|
||||
@Field(id = 1) final Intent intent;
|
||||
|
||||
private Map<String, String> dataCache;
|
||||
|
||||
@Constructor
|
||||
public CloudMessage(@NonNull @Param(id = 1) Intent intent) {
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCollapseKey() {
|
||||
return intent.getStringExtra("collapse_key");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized Map<String, String> getData() {
|
||||
if (dataCache == null) {
|
||||
dataCache = parseExtrasToMap(intent.getExtras());
|
||||
}
|
||||
return dataCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFrom() {
|
||||
return intent.getStringExtra("from");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Intent getIntent() {
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessageId() {
|
||||
String msgId = intent.getStringExtra("google.message_id");
|
||||
return msgId == null ? intent.getStringExtra("message_id") : msgId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessageType() {
|
||||
return intent.getStringExtra("message_type");
|
||||
}
|
||||
|
||||
@MessagePriority
|
||||
public int getOriginalPriority() {
|
||||
String p = intent.getStringExtra("google.original_priority");
|
||||
if (p == null) {
|
||||
p = intent.getStringExtra("google.priority");
|
||||
}
|
||||
return parsePriority(p);
|
||||
}
|
||||
|
||||
@MessagePriority
|
||||
public int getPriority() {
|
||||
String p = intent.getStringExtra("google.delivered_priority");
|
||||
if (p == null) {
|
||||
if ("1".equals(intent.getStringExtra("google.priority_reduced"))) {
|
||||
return PRIORITY_NORMAL;
|
||||
}
|
||||
p = intent.getStringExtra("google.priority");
|
||||
}
|
||||
return parsePriority(p);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] getRawData() {
|
||||
return intent.getByteArrayExtra("rawData");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSenderId() {
|
||||
return intent.getStringExtra("google.c.sender.id");
|
||||
}
|
||||
|
||||
public long getSentTime() {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
return 0;
|
||||
}
|
||||
Object raw = extras.get("google.sent_time");
|
||||
if (raw instanceof Long) {
|
||||
return (Long) raw;
|
||||
}
|
||||
if (raw instanceof String) {
|
||||
try {
|
||||
return Long.parseLong((String) raw);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Invalid sent time: " + raw);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTo() {
|
||||
return intent.getStringExtra("google.to");
|
||||
}
|
||||
|
||||
public int getTtl() {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
return 0;
|
||||
}
|
||||
Object raw = extras.get("google.ttl");
|
||||
if (raw instanceof Integer) {
|
||||
return (Integer) raw;
|
||||
}
|
||||
if (raw instanceof String) {
|
||||
try {
|
||||
return Integer.parseInt((String) raw);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Invalid TTL: " + raw);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Integer getProductId() {
|
||||
if (!intent.hasExtra("google.product_id")) {
|
||||
return null;
|
||||
}
|
||||
return intent.getIntExtra("google.product_id", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
CloudMessageCreator.writeToParcel(this, dest, flags);
|
||||
}
|
||||
|
||||
@MessagePriority
|
||||
private static int parsePriority(@Nullable String p) {
|
||||
if ("high".equals(p)) {
|
||||
return PRIORITY_HIGH;
|
||||
} else if ("normal".equals(p)) {
|
||||
return PRIORITY_NORMAL;
|
||||
} else {
|
||||
return PRIORITY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Map<String, String> parseExtrasToMap(@Nullable Bundle extras) {
|
||||
ArrayMap<String, String> map = new ArrayMap<>();
|
||||
if (extras == null) {
|
||||
return map;
|
||||
}
|
||||
for (String key : extras.keySet()) {
|
||||
Object value = extras.get(key);
|
||||
if (!(value instanceof String)
|
||||
|| key.startsWith("google.")
|
||||
|| key.equals("from")
|
||||
|| key.equals("message_type")
|
||||
|| key.equals("collapse_key"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
map.put(key, (String) value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class CloudMessageBundle {
|
||||
|
||||
private CloudMessageBundle() {}
|
||||
|
||||
@NonNull
|
||||
public static Bundle forMessage(@NonNull CloudMessage message) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("google.message_id", message.getMessageId());
|
||||
|
||||
Integer productId = message.getProductId();
|
||||
if (productId != null) {
|
||||
bundle.putInt("google.product_id", productId);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
final class CloudMessagingException extends Exception {
|
||||
|
||||
public CloudMessagingException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CloudMessagingException(@Nullable String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.android.gms.common.util.concurrent.NamedThreadFactory;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Keep
|
||||
public abstract class CloudMessagingReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "CloudMessagingReceiver";
|
||||
|
||||
public static final class IntentActionKeys {
|
||||
private static final String NOTIFICATION_OPEN = "com.google.firebase.messaging.NOTIFICATION_OPEN";
|
||||
private static final String NOTIFICATION_DISMISS = "com.google.firebase.messaging.NOTIFICATION_DISMISS";
|
||||
}
|
||||
|
||||
public static final class IntentKeys {
|
||||
private static final String PENDING_INTENT = "pending_intent";
|
||||
private static final String WRAPPED_INTENT = "wrapped_intent";
|
||||
}
|
||||
|
||||
private static ThreadPoolExecutor singleThreadExecutor(String threadName) {
|
||||
return new ThreadPoolExecutor(
|
||||
0, 1, 30, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory(threadName)
|
||||
);
|
||||
}
|
||||
|
||||
private static final class ExecutorsHolder {
|
||||
static final ThreadPoolExecutor dispatchExecutor
|
||||
= singleThreadExecutor("firebase-iid-executor");
|
||||
|
||||
static final ThreadPoolExecutor ackExecutor
|
||||
= singleThreadExecutor("pscm-ack-executor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onReceive(Context context, Intent intent) {
|
||||
if (intent != null) {
|
||||
BroadcastReceiver.PendingResult pendingResult = goAsync();
|
||||
getBroadcastExecutor().execute(
|
||||
() -> handleIntent(intent, context, isOrderedBroadcast(), pendingResult)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Executor getBroadcastExecutor() {
|
||||
return ExecutorsHolder.dispatchExecutor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Executor getAckExecutor() {
|
||||
return ExecutorsHolder.ackExecutor;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
protected abstract int onMessageReceive(@NonNull Context context, @NonNull CloudMessage message);
|
||||
|
||||
@WorkerThread
|
||||
protected void onNotificationDismissed(@NonNull Context context, @NonNull Bundle data) {}
|
||||
|
||||
@WorkerThread
|
||||
private void handleIntent(@NonNull Intent intent,
|
||||
@NonNull Context context,
|
||||
boolean isOrdered,
|
||||
BroadcastReceiver.PendingResult pendingResult)
|
||||
{
|
||||
int resultCode = 500;
|
||||
|
||||
Parcelable wrappedIntent = intent.getParcelableExtra(IntentKeys.WRAPPED_INTENT);
|
||||
|
||||
try {
|
||||
if (wrappedIntent instanceof Intent) {
|
||||
if (BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
|
||||
resultCode = deliverDismissNotificationAction((Intent) wrappedIntent, context);
|
||||
}
|
||||
} else {
|
||||
if (intent.getExtras() != null) {
|
||||
resultCode = deliverCloudMessage(intent, context);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (pendingResult != null) {
|
||||
if (isOrdered) {
|
||||
pendingResult.setResultCode(resultCode);
|
||||
}
|
||||
pendingResult.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private int deliverCloudMessage(@NonNull Intent intent, @NonNull Context context) {
|
||||
CloudMessage message = new CloudMessage(intent);
|
||||
|
||||
CountDownLatch ackLatch = new CountDownLatch(1);
|
||||
getAckExecutor().execute(() -> {
|
||||
if (TextUtils.isEmpty(message.getMessageId())) {
|
||||
ackLatch.countDown();
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle data = CloudMessageBundle.forMessage(message);
|
||||
data.putBoolean("supports_message_handled", true);
|
||||
|
||||
MessengerIpcClient
|
||||
.getInstance(context)
|
||||
.sendOneWay(GmsOpCode.BROADCAST_ACK, data)
|
||||
.addOnCompleteListener(
|
||||
Runnable::run, t -> ackLatch.countDown()
|
||||
);
|
||||
});
|
||||
|
||||
int result = onMessageReceive(context, message);
|
||||
|
||||
try {
|
||||
boolean acked = ackLatch.await(1, TimeUnit.SECONDS);
|
||||
if (!acked) {
|
||||
Log.w(TAG, "Message ack timed out");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Message ack failed: " + e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private int deliverDismissNotificationAction(@NonNull Intent intent, @NonNull Context context) {
|
||||
PendingIntent pendingIntent = intent.getParcelableExtra(IntentKeys.PENDING_INTENT);
|
||||
if (pendingIntent != null) {
|
||||
try {
|
||||
pendingIntent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Notification pending intent canceled");
|
||||
}
|
||||
}
|
||||
|
||||
if (!IntentActionKeys.NOTIFICATION_DISMISS.equals(intent.getAction())) {
|
||||
return 500;
|
||||
}
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null) {
|
||||
extras.remove(IntentKeys.PENDING_INTENT);
|
||||
} else {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
onNotificationDismissed(context, extras);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
final class GmsOpCode {
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = { SEND, BROADCAST_ACK, RPC_ACK, PROXY_RETAIN, PROXY_FETCH })
|
||||
@interface Code {}
|
||||
|
||||
static final int SEND = 1;
|
||||
static final int BROADCAST_ACK = 2;
|
||||
static final int RPC_ACK = 3;
|
||||
static final int PROXY_RETAIN = 4;
|
||||
static final int PROXY_FETCH = 5;
|
||||
|
||||
private GmsOpCode() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.common.stats.ConnectionTracker;
|
||||
import com.google.android.gms.common.util.concurrent.NamedThreadFactory;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.TaskCompletionSource;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class MessengerIpcClient {
|
||||
|
||||
private static final String TAG = "MessengerIpcClient";
|
||||
|
||||
private static final String ACTION_REGISTER = "com.google.android.c2dm.intent.REGISTER";
|
||||
|
||||
private static final long TIMEOUT_SECONDS = 30L;
|
||||
|
||||
@Nullable
|
||||
private static volatile MessengerIpcClient instance;
|
||||
|
||||
private final Context context;
|
||||
private final ScheduledExecutorService executor;
|
||||
private final AtomicInteger nextRequestId;
|
||||
|
||||
private Connection connection;
|
||||
|
||||
private MessengerIpcClient(Context context, ScheduledExecutorService executor) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.executor = executor;
|
||||
this.nextRequestId = new AtomicInteger(1);
|
||||
this.connection = new Connection();
|
||||
}
|
||||
|
||||
public static MessengerIpcClient getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (MessengerIpcClient.class) {
|
||||
if (instance == null) {
|
||||
instance = new MessengerIpcClient(
|
||||
context,
|
||||
Executors.unconfigurableScheduledExecutorService(
|
||||
Executors.newScheduledThreadPool(1, new NamedThreadFactory(TAG))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Task<Bundle> sendRequest(@GmsOpCode.Code int what, Bundle data) {
|
||||
return enqueue(new TwoWayRequest(nextRequestId(), what, data));
|
||||
}
|
||||
|
||||
public Task<Void> sendOneWay(@GmsOpCode.Code int what, Bundle data) {
|
||||
return enqueue(new OneWayRequest(nextRequestId(), what, data));
|
||||
}
|
||||
|
||||
private <T> Task<T> enqueue(Request<T> request) {
|
||||
synchronized (this) {
|
||||
if (!connection.enqueueRequest(request)) {
|
||||
connection = new Connection();
|
||||
connection.enqueueRequest(request);
|
||||
}
|
||||
}
|
||||
return request.completion.getTask();
|
||||
}
|
||||
|
||||
private int nextRequestId() {
|
||||
return nextRequestId.getAndIncrement();
|
||||
}
|
||||
|
||||
private class Connection implements ServiceConnection {
|
||||
|
||||
private enum State { IDLE, BINDING, BOUND, UNBINDING, DEAD }
|
||||
|
||||
private State state = State.IDLE;
|
||||
|
||||
private final Messenger replyMessenger;
|
||||
private Messenger serviceMessenger;
|
||||
|
||||
private final ConnectionTracker connectionTracker = ConnectionTracker.getInstance();
|
||||
|
||||
private final Queue<Request<?>> pendingRequests = new ArrayDeque<>();
|
||||
private final SparseArray<Request<?>> activeRequests = new SparseArray<>();
|
||||
|
||||
private Connection() {
|
||||
replyMessenger = new Messenger(
|
||||
new Handler(Looper.getMainLooper(), this::handleReply)
|
||||
);
|
||||
}
|
||||
|
||||
synchronized boolean enqueueRequest(Request<?> request) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
state = State.BINDING;
|
||||
pendingRequests.add(request);
|
||||
bindService();
|
||||
break;
|
||||
case BINDING:
|
||||
pendingRequests.add(request);
|
||||
break;
|
||||
case BOUND:
|
||||
pendingRequests.add(request);
|
||||
sendPendingRequests();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void bindService() {
|
||||
Log.v(TAG, "Starting bind to GmsCore");
|
||||
|
||||
final Intent intent = new Intent(ACTION_REGISTER);
|
||||
intent.setPackage(GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE);
|
||||
|
||||
try {
|
||||
boolean bound = connectionTracker.bindService(context, intent, this, Context.BIND_AUTO_CREATE);
|
||||
if (bound) {
|
||||
executor.schedule(this::onBindTimeout, TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} else {
|
||||
handleDisconnection("Unable to bind to service");
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
handleDisconnection("Unable to bind to service", e);
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||
Log.v(TAG, "Service connected");
|
||||
executor.execute(() -> handleBound(binder));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.v(TAG, "Service disconnected");
|
||||
executor.execute(() -> handleDisconnection("Service disconnected"));
|
||||
}
|
||||
|
||||
private synchronized void onBindTimeout() {
|
||||
if (state == State.BINDING) {
|
||||
handleDisconnection("Timed out while binding");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void handleBound(IBinder binder) {
|
||||
try {
|
||||
if (!"android.os.IMessenger".equals(binder.getInterfaceDescriptor())) {
|
||||
throw new RemoteException("Invalid interface descriptor");
|
||||
}
|
||||
serviceMessenger = new Messenger(binder);
|
||||
state = State.BOUND;
|
||||
sendPendingRequests();
|
||||
} catch (RemoteException e) {
|
||||
handleDisconnection("Service connection failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void handleDisconnection(@Nullable String reason) {
|
||||
handleDisconnection(reason, null);
|
||||
}
|
||||
|
||||
private synchronized void handleDisconnection(@Nullable String reason, @Nullable Throwable cause) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
throw new IllegalStateException();
|
||||
case BINDING:
|
||||
case BOUND:
|
||||
state = State.DEAD;
|
||||
connectionTracker.unbindService(context, this);
|
||||
failAndClearAll(reason, cause);
|
||||
break;
|
||||
case UNBINDING:
|
||||
state = State.DEAD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void failAndClearAll(@Nullable String reason, @Nullable Throwable cause) {
|
||||
final CloudMessagingException e = new CloudMessagingException(reason, cause);
|
||||
|
||||
for (Request<?> pendingRequest : pendingRequests) {
|
||||
pendingRequest.fail(e);
|
||||
}
|
||||
pendingRequests.clear();
|
||||
|
||||
for (int i = 0; i < activeRequests.size(); i++) {
|
||||
activeRequests.valueAt(i).fail(e);
|
||||
}
|
||||
activeRequests.clear();
|
||||
}
|
||||
|
||||
private void sendPendingRequests() {
|
||||
executor.execute(() -> {
|
||||
while (true) {
|
||||
Request<?> request;
|
||||
|
||||
synchronized (this) {
|
||||
if (state != State.BOUND) {
|
||||
return;
|
||||
}
|
||||
request = pendingRequests.poll();
|
||||
if (request == null) {
|
||||
maybeUnbind();
|
||||
return;
|
||||
}
|
||||
activeRequests.put(request.id, request);
|
||||
executor.schedule(() -> timeoutRequest(request.id), TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.what = request.what;
|
||||
msg.arg1 = request.id;
|
||||
msg.replyTo = replyMessenger;
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putBoolean("oneWay", request.isOneWay());
|
||||
data.putString("pkg", context.getPackageName());
|
||||
data.putBundle("data", request.payload);
|
||||
msg.setData(data);
|
||||
|
||||
try {
|
||||
serviceMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
handleDisconnection(e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void timeoutRequest(int requestId) {
|
||||
Request<?> r = activeRequests.get(requestId);
|
||||
if (r != null) {
|
||||
Log.w(TAG, "Timing out request: " + requestId);
|
||||
activeRequests.remove(requestId);
|
||||
r.fail(new CloudMessagingException("Timed out waiting for response"));
|
||||
maybeUnbind();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleReply(Message msg) {
|
||||
final Request<?> request;
|
||||
|
||||
synchronized (this) {
|
||||
int requestId = msg.arg1;
|
||||
request = activeRequests.get(requestId);
|
||||
if (request == null) {
|
||||
Log.w(TAG, "Unknown request response: " + requestId);
|
||||
return true;
|
||||
}
|
||||
activeRequests.remove(requestId);
|
||||
maybeUnbind();
|
||||
}
|
||||
|
||||
Bundle data = msg.getData();
|
||||
if (data.getBoolean("unsupported", false)) {
|
||||
request.fail(new CloudMessagingException("Not supported by GmsCore"));
|
||||
} else {
|
||||
request.onResponse(data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized void maybeUnbind() {
|
||||
if (state == State.BOUND && activeRequests.size() == 0 && pendingRequests.isEmpty()) {
|
||||
Log.v(TAG, "Finished handling requests, unbinding");
|
||||
state = State.UNBINDING;
|
||||
connectionTracker.unbindService(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class Request<T> {
|
||||
|
||||
final int id;
|
||||
final int what;
|
||||
final Bundle payload;
|
||||
|
||||
final TaskCompletionSource<T> completion = new TaskCompletionSource<>();
|
||||
|
||||
Request(int id, int what, Bundle payload) {
|
||||
this.id = id;
|
||||
this.what = what;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
final void fail(CloudMessagingException e) {
|
||||
completion.setException(e);
|
||||
}
|
||||
|
||||
final void complete(T result) {
|
||||
completion.setResult(result);
|
||||
}
|
||||
|
||||
abstract boolean isOneWay();
|
||||
|
||||
abstract void onResponse(Bundle response);
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "Request { what=" + what + " id=" + id + " oneWay=" + isOneWay() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
static final class OneWayRequest extends Request<Void> {
|
||||
OneWayRequest(int id, int what, Bundle payload) {
|
||||
super(id, what, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOneWay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onResponse(Bundle response) {
|
||||
if (response.getBoolean("ack", false)) {
|
||||
complete(null);
|
||||
} else {
|
||||
fail(new CloudMessagingException("Invalid response to one way request"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class TwoWayRequest extends Request<Bundle> {
|
||||
TwoWayRequest(int id, int what, Bundle payload) {
|
||||
super(id, what, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOneWay() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onResponse(Bundle response) {
|
||||
Bundle data = response.getBundle("data");
|
||||
if (data == null) {
|
||||
data = Bundle.EMPTY;
|
||||
}
|
||||
complete(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.wrappers.PackageManagerWrapper;
|
||||
import com.google.android.gms.common.wrappers.Wrappers;
|
||||
|
||||
import static com.google.android.gms.common.GoogleApiAvailabilityLight.GOOGLE_PLAY_SERVICES_PACKAGE;
|
||||
|
||||
final class Metadata {
|
||||
|
||||
private static final String TAG = "Metadata";
|
||||
|
||||
private final PackageManagerWrapper packageManager;
|
||||
|
||||
private Integer gmsPackageVersion;
|
||||
|
||||
public Metadata(Context context) {
|
||||
this.packageManager = Wrappers.packageManager(context);
|
||||
}
|
||||
|
||||
public int getGmsPackageVersion() {
|
||||
if (gmsPackageVersion == null) {
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(GOOGLE_PLAY_SERVICES_PACKAGE, 0);
|
||||
gmsPackageVersion = packageInfo.versionCode;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Failed to find package: " + e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return gmsPackageVersion;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.google.android.gms.cloudmessaging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.Tasks;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Keep
|
||||
public class Rpc {
|
||||
|
||||
private static final String TAG = "Rpc";
|
||||
|
||||
private static final String SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
|
||||
|
||||
private static final int MIN_VERSION_FOR_MESSAGE_ACK = 233700000;
|
||||
private static final int MIN_VERSION_FOR_PROXY_RETENTION = 241100000;
|
||||
|
||||
private final MessengerIpcClient ipcClient;
|
||||
private final Metadata metadata;
|
||||
|
||||
public Rpc(@NonNull Context context) {
|
||||
this.ipcClient = MessengerIpcClient.getInstance(context);
|
||||
this.metadata = new Metadata(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Task<Bundle> send(@NonNull Bundle data) {
|
||||
return ipcClient
|
||||
.sendRequest(GmsOpCode.SEND, data)
|
||||
.continueWith(Runnable::run, task -> {
|
||||
if (task.isSuccessful()) {
|
||||
return task.getResult();
|
||||
} else {
|
||||
Exception e = task.getException();
|
||||
Log.d(TAG, "Error making request: " + e);
|
||||
throw new IOException(SERVICE_NOT_AVAILABLE, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Task<Void> messageHandled(@NonNull CloudMessage message) {
|
||||
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_MESSAGE_ACK) {
|
||||
return serviceNotAvailable();
|
||||
}
|
||||
Bundle response = CloudMessageBundle.forMessage(message);
|
||||
return ipcClient.sendOneWay(GmsOpCode.RPC_ACK, response);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Task<Void> setRetainProxiedNotifications(boolean retain) {
|
||||
if (!BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
|
||||
return serviceNotAvailable();
|
||||
}
|
||||
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_PROXY_RETENTION) {
|
||||
return serviceNotAvailable();
|
||||
}
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean("proxy_retention", retain);
|
||||
return ipcClient.sendOneWay(GmsOpCode.PROXY_RETAIN, bundle);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Task<CloudMessage> getProxiedNotificationData() {
|
||||
if (!BuildConfig.NOTIFICATION_PAYLOAD_ENABLED) {
|
||||
return serviceNotAvailable();
|
||||
}
|
||||
if (metadata.getGmsPackageVersion() < MIN_VERSION_FOR_PROXY_RETENTION) {
|
||||
return serviceNotAvailable();
|
||||
}
|
||||
return ipcClient
|
||||
.sendRequest(GmsOpCode.PROXY_FETCH, Bundle.EMPTY)
|
||||
.continueWith(Runnable::run, task -> {
|
||||
Bundle bundle = task.getResult();
|
||||
Parcelable intent = bundle.getParcelable("notification_data");
|
||||
if (!(intent instanceof Intent)) {
|
||||
return null;
|
||||
}
|
||||
return new CloudMessage((Intent) intent);
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static <T> Task<T> serviceNotAvailable() {
|
||||
return Tasks.forException(new IOException(SERVICE_NOT_AVAILABLE));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.google.firebase;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class FirebaseException extends Exception {
|
||||
|
||||
@Deprecated
|
||||
protected FirebaseException() {}
|
||||
|
||||
public FirebaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FirebaseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.google.firebase.iid.internal;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Keep
|
||||
public interface FirebaseInstanceIdInternal {
|
||||
|
||||
void deleteToken(@NonNull String senderId, @NonNull String scope) throws IOException;
|
||||
|
||||
String getId();
|
||||
|
||||
@Nullable
|
||||
String getToken();
|
||||
|
||||
@NonNull
|
||||
Task<String> getTokenTask();
|
||||
|
||||
void addNewTokenListener(NewTokenListener listener);
|
||||
|
||||
interface NewTokenListener {
|
||||
void onNewToken(String token);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue