chromium/chrome/android/java/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsAdapter.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.browserservices.digitalgoods;

import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;

import androidx.browser.trusted.TrustedWebActivityCallback;

import org.chromium.base.Log;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClientWrappers;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.payments.mojom.DigitalGoods.Consume_Response;
import org.chromium.payments.mojom.DigitalGoods.GetDetails_Response;
import org.chromium.payments.mojom.DigitalGoods.ListPurchaseHistory_Response;
import org.chromium.payments.mojom.DigitalGoods.ListPurchases_Response;

/**
 * This class uses the {@link DigitalGoodsConverter} to convert data types between mojo types and
 * Android types and then uses the {@link TrustedWebActivityClient} to call into the Trusted Web
 * Activity Client.
 */
public class DigitalGoodsAdapter {
    private static final String TAG = "DigitalGoods";

    public static final String COMMAND_CONSUME = "consume";
    public static final String COMMAND_GET_DETAILS = "getDetails";
    public static final String COMMAND_LIST_PURCHASES = "listPurchases";
    public static final String COMMAND_LIST_PURCHASE_HISTORY = "listPurchaseHistory";
    public static final String KEY_SUCCESS = "success";

    // Legacy commands kept around for backwards compatibility.
    public static final String COMMAND_ACKNOWLEDGE = "acknowledge";

    private final TrustedWebActivityClient mClient;

    public DigitalGoodsAdapter(TrustedWebActivityClient client) {
        mClient = client;
    }

    public void getDetails(Uri scope, String[] itemIds, GetDetails_Response response) {
        Bundle args = GetDetailsConverter.convertParams(itemIds);
        TrustedWebActivityCallback callback = GetDetailsConverter.convertCallback(response);
        Runnable onError = () -> GetDetailsConverter.returnClientAppError(response);
        Runnable onUnavailable = () -> GetDetailsConverter.returnClientAppUnavailable(response);

        execute(scope, COMMAND_GET_DETAILS, args, callback, onError, onUnavailable);
    }

    public void listPurchases(Uri scope, ListPurchases_Response response) {
        Bundle args = new Bundle();
        TrustedWebActivityCallback callback = ListPurchasesConverter.convertCallback(response);
        Runnable onError = () -> ListPurchasesConverter.returnClientAppError(response);
        Runnable onUnavailable = () -> ListPurchasesConverter.returnClientAppUnavailable(response);

        execute(scope, COMMAND_LIST_PURCHASES, args, callback, onError, onUnavailable);
    }

    public void listPurchaseHistory(Uri scope, ListPurchaseHistory_Response response) {
        Bundle args = new Bundle();
        TrustedWebActivityCallback callback =
                ListPurchaseHistoryConverter.convertCallback(response);
        Runnable onError = () -> ListPurchaseHistoryConverter.returnClientAppError(response);
        Runnable onUnavailable =
                () -> ListPurchaseHistoryConverter.returnClientAppUnavailable(response);

        execute(scope, COMMAND_LIST_PURCHASE_HISTORY, args, callback, onError, onUnavailable);
    }

    public void consume(Uri scope, String purchaseToken, Consume_Response response) {
        Bundle args = ConsumeConverter.convertParams(purchaseToken);
        TrustedWebActivityCallback callback = ConsumeConverter.convertCallback(response);
        Runnable onUnavailable = () -> ConsumeConverter.returnClientAppUnavailable(response);
        Runnable onError = () -> ConsumeConverter.returnClientAppError(response);

        // If Consume fails, try to call acknowledge(..., makeAvailableAgain = true) which will
        // achieve the same effect on older clients.
        Runnable tryAcknowledgeOnError =
                () -> {
                    Bundle ackArgs = AcknowledgeConverter.convertParams(purchaseToken);
                    TrustedWebActivityCallback ackCallback =
                            AcknowledgeConverter.convertCallback(response);

                    execute(
                            scope,
                            COMMAND_ACKNOWLEDGE,
                            ackArgs,
                            ackCallback,
                            onError,
                            onUnavailable);
                };

        execute(scope, COMMAND_CONSUME, args, callback, tryAcknowledgeOnError, onUnavailable);
    }

    private void execute(
            Uri scope,
            String command,
            Bundle args,
            TrustedWebActivityCallback callback,
            Runnable onClientAppError,
            Runnable onClientAppUnavailable) {
        mClient.connectAndExecute(
                scope,
                new TrustedWebActivityClient.ExecutionCallback() {
                    @Override
                    public void onConnected(
                            Origin origin, TrustedWebActivityClientWrappers.Connection service)
                            throws RemoteException {
                        // Wrap this call so that crashes in the TWA client don't cause crashes in
                        // Chrome.
                        Bundle result = null;
                        try {
                            result = service.sendExtraCommand(command, args, callback);
                        } catch (Exception e) {
                            Log.w(TAG, "Exception communicating with client.");
                            onClientAppError.run();
                        }

                        boolean success = result != null && result.getBoolean(KEY_SUCCESS, false);
                        if (!success) {
                            onClientAppError.run();
                        }
                    }

                    @Override
                    public void onNoTwaFound() {
                        Log.w(TAG, "Unable to execute " + command + ".");
                        onClientAppUnavailable.run();
                    }
                });
    }
}