chromium/chrome/android/java/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsConverter.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.os.Bundle;
import android.os.Parcelable;

import androidx.annotation.Nullable;

import org.chromium.base.Log;
import org.chromium.payments.mojom.BillingResponseCode;

import java.util.ArrayList;
import java.util.List;

/**
 * The *Converter classes take care of converting between the mojo types that
 * {@link DigitalGoodsImpl} deals with and the Android types that {@link TrustedWebActivityClient}
 * details with.
 *
 * Ideally these classes would have no Chromium dependencies that are not from Mojo (in a *.mojom.*
 * package) to allow it to be more easily reused in ARC++.
 */
public class DigitalGoodsConverter {
    private static final String TAG = "DigitalGoods";

    static final String KEY_VERSION = "digitalgoods.version";

    // These values are copied from the Play Billing library since Chrome cannot depend on it.
    // https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode
    static final int PLAY_BILLING_OK = 0;
    static final int PLAY_BILLING_ITEM_ALREADY_OWNED = 7;
    static final int PLAY_BILLING_ITEM_NOT_OWNED = 8;
    static final int PLAY_BILLING_ITEM_UNAVAILABLE = 4;

    private DigitalGoodsConverter() {}

    /** Converts the given response code to one suitable for mojo. */
    static int convertResponseCode(int responseCode, Bundle bundle) {
        // In the initial development, the TWA shell provided a Play Billing response code, so it
        // needs to be converted to a Mojo one. Later on (but still before the feature was publicly
        // launched), we decided that the TWA shell should provide data to Chrome already converted
        // to a Mojo format. This is because the TWA shell may not be using Play Billing, so it
        // doesn't make sense to standardise on that.

        // We kept support for the older version just to make testing and development easier. It may
        // be removed once the feature has launched.
        int version = bundle.getInt(KEY_VERSION);
        if (version == 0) {
            return playBillingToMojoResponseCode(responseCode);
        }

        if (BillingResponseCode.isKnownValue(responseCode)) {
            return responseCode;
        }

        Log.w(TAG, "Unexpected response code: " + responseCode);
        return BillingResponseCode.ERROR;
    }

    static int playBillingToMojoResponseCode(int responseCode) {
        switch (responseCode) {
            case PLAY_BILLING_OK:
                return BillingResponseCode.OK;
            case PLAY_BILLING_ITEM_ALREADY_OWNED:
                return BillingResponseCode.ITEM_ALREADY_OWNED;
            case PLAY_BILLING_ITEM_NOT_OWNED:
                return BillingResponseCode.ITEM_NOT_OWNED;
            case PLAY_BILLING_ITEM_UNAVAILABLE:
                return BillingResponseCode.ITEM_UNAVAILABLE;
            default:
                Log.w(TAG, "Unexpected response code: " + responseCode);
                return BillingResponseCode.ERROR;
        }
    }

    /** Checks that the given field exists and is of the required type in a Bundle. */
    static <T> boolean checkField(Bundle bundle, String key, Class<T> clazz) {
        if (bundle.containsKey(key) && clazz.isAssignableFrom(bundle.get(key).getClass())) {
            return true;
        }
        Log.w(TAG, "Missing field " + key + " of type " + clazz.getName() + ".");
        return false;
    }

    /** An interface for use with {@link #convertParcelableArray}. */
    interface Converter<T> {
        @Nullable
        T convert(Bundle bundle);
    }

    /**
     * Runs through the given {@link Parcelable[]} and for each item that is a {@link Bundle}, calls
     * {@link Converter#convert}, returning an array of the non-null results.
     */
    static <T> List<T> convertParcelableArray(Parcelable[] array, Converter<T> converter) {
        List<T> list = new ArrayList<>();
        for (Parcelable item : array) {
            if (!(item instanceof Bundle)) {
                Log.w(TAG, "Passed a Parcelable that was not a Bundle.");
                continue;
            }

            T converted = converter.convert((Bundle) item);
            if (converted != null) list.add(converted);
        }

        return list;
    }
}