*/
String EXTRA_API_ERROR_MESSAGE = "extra_api_error_message";
+ /**
+ * Checks the e-money readiness for a service provider and account.
+ *
+ * Important: Only apps on the allowlist for the e-money feature can call this method; otherwise, the result is a failed {@link Task}.
+ *
+ * @param serviceProvider The service provider to check readiness.
+ * @param accountName The email account to check readiness.
+ * @return One of the possible {@link EmoneyReadinessStatus}.
+ */
+ Task checkReadinessForEmoney(String serviceProvider, String accountName);
+
/**
* Gets the {@link PayApiAvailabilityStatus} of the current user and device.
*
@@ -51,6 +64,34 @@ public interface PayClient extends HasApiKey
*/
PayClient.ProductName getProductName();
+ /**
+ * Notifies Google Play services of a card tap event.
+ *
+ * Only apps on the allowlist can call this method; otherwise, the result is a failed {@link Task}.
+ *
+ * @param eventJson The event details in JSON format.
+ */
+ Task notifyCardTapEvent(String eventJson);
+
+ /**
+ * Notifies Google Play services if an e-money card has been updated.
+ *
+ * Important: Only apps on the allowlist for the e-money feature can call this method; otherwise, the result is a failed {@link Task}.
+ *
+ * @param json The e-money card status update details in JSON format.
+ */
+ Task notifyEmoneyCardStatusUpdate(String json);
+
+ /**
+ * Saves an e-money card in JSON format.
+ *
+ * Important: Only apps on the allowlist for the e-money feature can call this method; otherwise, the result is a failed {@link Task}.
+ *
+ * @param json The e-money card details in JSON format.
+ * @param activityResultLauncher an {@link ActivityResultLauncher} registered by caller to handle the activity results.
+ */
+ Task pushEmoneyCard(String json, ActivityResultLauncher activityResultLauncher);
+
/**
* Saves one or multiple passes in a JSON format.
*
diff --git a/play-services-pay/src/main/java/com/google/android/gms/pay/PushEmoneyCardRequest.java b/play-services-pay/src/main/java/com/google/android/gms/pay/PushEmoneyCardRequest.java
new file mode 100644
index 0000000000..609bc23374
--- /dev/null
+++ b/play-services-pay/src/main/java/com/google/android/gms/pay/PushEmoneyCardRequest.java
@@ -0,0 +1,27 @@
+package com.google.android.gms.pay;
+
+import android.os.Parcel;
+
+import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter;
+import org.microg.gms.common.Hide;
+
+@Hide
+@SafeParcelable.Class
+public class PushEmoneyCardRequest extends AbstractSafeParcelable {
+ @Field(1)
+ public String json;
+
+ @Constructor
+ public PushEmoneyCardRequest(@Param(1) String json) {
+ this.json = json;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ CREATOR.writeToParcel(this, out, flags);
+ }
+
+ public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(PushEmoneyCardRequest.class);
+}
diff --git a/play-services-pay/src/main/java/com/google/android/gms/pay/SavePassesRequest.java b/play-services-pay/src/main/java/com/google/android/gms/pay/SavePassesRequest.java
new file mode 100644
index 0000000000..56a83f7329
--- /dev/null
+++ b/play-services-pay/src/main/java/com/google/android/gms/pay/SavePassesRequest.java
@@ -0,0 +1,33 @@
+package com.google.android.gms.pay;
+
+import android.os.Parcel;
+
+import androidx.annotation.Nullable;
+import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter;
+import org.microg.gms.common.Hide;
+
+@Hide
+@SafeParcelable.Class
+public class SavePassesRequest extends AbstractSafeParcelable {
+ @Nullable
+ @Field(1)
+ public String json;
+ @Nullable
+ @Field(2)
+ public String jwt;
+
+ @Constructor
+ public SavePassesRequest(@Nullable @Param(1) String json, @Nullable @Param(2) String jwt) {
+ this.json = json;
+ this.jwt = jwt;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ CREATOR.writeToParcel(this, out, flags);
+ }
+
+ public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SavePassesRequest.class);
+}
diff --git a/play-services-pay/src/main/java/com/google/android/gms/pay/SyncBundleRequest.java b/play-services-pay/src/main/java/com/google/android/gms/pay/SyncBundleRequest.java
new file mode 100644
index 0000000000..0c20031d46
--- /dev/null
+++ b/play-services-pay/src/main/java/com/google/android/gms/pay/SyncBundleRequest.java
@@ -0,0 +1,27 @@
+package com.google.android.gms.pay;
+
+import android.os.Parcel;
+
+import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelable;
+import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter;
+import org.microg.gms.common.Hide;
+
+@Hide
+@SafeParcelable.Class
+public class SyncBundleRequest extends AbstractSafeParcelable {
+ @Field(1)
+ public String unknownField;
+
+ @Constructor
+ public SyncBundleRequest(@Param(1) String unknownField) {
+ this.unknownField = unknownField;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ CREATOR.writeToParcel(this, out, flags);
+ }
+
+ public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SyncBundleRequest.class);
+}
diff --git a/play-services-pay/src/main/java/org/microg/gms/pay/PayClientImpl.java b/play-services-pay/src/main/java/org/microg/gms/pay/PayClientImpl.java
index 8817b2c9fc..0f16c660aa 100644
--- a/play-services-pay/src/main/java/org/microg/gms/pay/PayClientImpl.java
+++ b/play-services-pay/src/main/java/org/microg/gms/pay/PayClientImpl.java
@@ -8,23 +8,59 @@
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.RemoteException;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.IntentSenderRequest;
import com.google.android.gms.common.api.Api;
+import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.GoogleApi;
-import com.google.android.gms.pay.PayApiAvailabilityStatus;
-import com.google.android.gms.pay.PayClient;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.pay.*;
+import com.google.android.gms.pay.internal.IPayServiceCallbacks;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
+import java.lang.ref.WeakReference;
+
public class PayClientImpl extends GoogleApi implements PayClient {
- private static final Api API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new PayApiClient(context, callbacks, connectionFailedListener));
+ private static final Api API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new ThirdPartyPayApiClient(context, callbacks, connectionFailedListener));
public PayClientImpl(Context context) {
super(context, API);
}
+ @Override
+ public Task checkReadinessForEmoney(String serviceProvider, String accountName) {
+ return null;
+ }
+
@Override
public Task<@PayApiAvailabilityStatus Integer> getPayApiAvailabilityStatus(@RequestType int requestType) {
- return Tasks.forResult(PayApiAvailabilityStatus.NOT_ELIGIBLE);
+ return this.scheduleTask((client, completionSource) -> {
+ client.getServiceInterface().getPayApiAvailabilityStatus(new GetPayApiAvailabilityStatusRequest(requestType), new PayServiceCallbacks() {
+ @Override
+ public void onPayApiAvailabilityStatus(Status status, int availabilityStatus) {
+ if (status.isSuccess() && availabilityStatus == 3) {
+ // Invalid availabilityStatus
+ completionSource.trySetException(new ApiException(Status.INTERNAL_ERROR));
+ } else if (availabilityStatus == 1) {
+ if (status.isSuccess()) {
+ completionSource.trySetResult(PayApiAvailabilityStatus.NOT_ELIGIBLE);
+ } else {
+ completionSource.trySetException(new ApiException(status));
+ }
+ } else {
+ if (status.isSuccess()) {
+ completionSource.trySetResult(availabilityStatus);
+ } else {
+ completionSource.trySetException(new ApiException(status));
+ }
+ }
+ }
+ });
+ });
}
@Override
@@ -38,12 +74,58 @@ public ProductName getProductName() {
}
@Override
- public void savePasses(String json, Activity activity, int requestCode) {
+ public Task notifyCardTapEvent(String eventJson) {
+ return null;
+ }
+ @Override
+ public Task notifyEmoneyCardStatusUpdate(String json) {
+ return null;
+ }
+
+ @Override
+ public Task pushEmoneyCard(String json, ActivityResultLauncher activityResultLauncher) {
+ return null;
+ }
+
+ @Override
+ public void savePasses(String json, Activity activity, int requestCode) {
+ savePasses(new SavePassesRequest(json, null), activity, requestCode);
}
@Override
public void savePassesJwt(String jwt, Activity activity, int requestCode) {
+ savePasses(new SavePassesRequest(null, jwt), activity, requestCode);
+ }
+ private void savePasses(SavePassesRequest request, Activity activity, int requestCode) {
+ // We don't want to keep a reference to the activity to not leak it
+ WeakReference weakActivity = new WeakReference<>(activity);
+ PayServiceCallbacks callbacks = new PayServiceCallbacks() {
+ @Override
+ public void onPendingIntent(Status status) {
+ Activity activity = weakActivity.get();
+ if (activity != null) {
+ if (status.hasResolution()) {
+ try {
+ status.startResolutionForResult(activity, requestCode);
+ } catch (IntentSender.SendIntentException e) {
+ // Ignored
+ }
+ } else {
+ PendingIntent resultIntent = activity.createPendingResult(requestCode, new Intent(), PendingIntent.FLAG_ONE_SHOT);
+ if (resultIntent != null) {
+ try {
+ resultIntent.send(status.isSuccess() ? -1 : status.getStatusCode());
+ } catch (PendingIntent.CanceledException e) {
+ // Ignored
+ }
+ }
+ }
+ }
+ }
+ };
+ this.scheduleTask((client, completionSource) -> client.getServiceInterface().savePasses(request, callbacks))
+ .addOnFailureListener((exception) -> callbacks.onPendingIntent(Status.INTERNAL_ERROR));
}
}
diff --git a/play-services-pay/src/main/java/org/microg/gms/pay/PayServiceCallbacks.java b/play-services-pay/src/main/java/org/microg/gms/pay/PayServiceCallbacks.java
new file mode 100644
index 0000000000..36e0094058
--- /dev/null
+++ b/play-services-pay/src/main/java/org/microg/gms/pay/PayServiceCallbacks.java
@@ -0,0 +1,59 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.pay;
+
+import android.app.PendingIntent;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.pay.EmoneyReadiness;
+import com.google.android.gms.pay.PayApiError;
+import com.google.android.gms.pay.internal.IPayServiceCallbacks;
+
+public class PayServiceCallbacks extends IPayServiceCallbacks.Stub {
+ @Override
+ public void onStatus(Status status) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onPendingIntentForWalletOnWear(Status status, PendingIntent pendingIntent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onStatusAndBoolean(Status status, boolean b) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onPayApiError(PayApiError error) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onStatusAndByteArray(Status status, byte[] bArr) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onStatusAndLong(Status status, long l) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onPendingIntent(Status status) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onPayApiAvailabilityStatus(Status status, int i) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onEmoneyReadiness(Status status, EmoneyReadiness emoneyReadiness) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/play-services-pay/src/main/java/org/microg/gms/pay/ThirdPartyPayApiClient.java b/play-services-pay/src/main/java/org/microg/gms/pay/ThirdPartyPayApiClient.java
new file mode 100644
index 0000000000..3e0eb9c239
--- /dev/null
+++ b/play-services-pay/src/main/java/org/microg/gms/pay/ThirdPartyPayApiClient.java
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.pay;
+
+import android.content.Context;
+import android.os.IBinder;
+import com.google.android.gms.pay.internal.IPayService;
+import com.google.android.gms.pay.internal.IThirdPartyPayService;
+import org.microg.gms.common.GmsClient;
+import org.microg.gms.common.GmsService;
+import org.microg.gms.common.api.ConnectionCallbacks;
+import org.microg.gms.common.api.OnConnectionFailedListener;
+
+public class ThirdPartyPayApiClient extends GmsClient {
+ public ThirdPartyPayApiClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) {
+ super(context, callbacks, connectionFailedListener, GmsService.PAY.SECONDARY_ACTIONS[0]);
+ serviceId = GmsService.PAY.SERVICE_ID;
+ }
+
+ @Override
+ protected IThirdPartyPayService interfaceFromBinder(IBinder binder) {
+ return IThirdPartyPayService.Stub.asInterface(binder);
+ }
+}