chromium/chrome/android/javatests/src/org/chromium/chrome/browser/payments/MockPackageManagerDelegate.java

// Copyright 2017 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.payments;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.graphics.drawable.Drawable;
import android.os.Bundle;

import androidx.annotation.Nullable;

import org.chromium.components.payments.AndroidPaymentAppFinder;
import org.chromium.components.payments.PackageManagerDelegate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** Simulates a package manager in memory. */
class MockPackageManagerDelegate extends PackageManagerDelegate {
    private static final int PAYMENT_METHOD_NAMES_STRING_ARRAY_RESOURCE_ID = 1;
    private static final int SUPPORTED_DELEGATIONS_STRING_ARRAY_RESOURCE_ID = 2;
    private static final int RESOURCES_SIZE = 2;

    private final List<ResolveInfo> mActivities = new ArrayList<>();
    private final Map<String, PackageInfo> mPackages = new HashMap<>();
    private final Map<ResolveInfo, CharSequence> mLabels = new HashMap<>();
    private final List<ResolveInfo> mServices = new ArrayList<>();
    private final Map<ApplicationInfo, List<String[]>> mResources = new HashMap<>();

    private String mInvokedAppPackageName;

    // A map of a package name to its installer's package name.
    private Map<String, String> mMockInstallerPackageMap = new HashMap<>();

    /**
     * Simulates an installed payment app with no supported delegations.
     *
     * @param label The user visible name of the app.
     * @param packageName The identifying package name.
     * @param defaultPaymentMethodName The name of the default payment method name for this app. If
     *     null, then this app will not have metadata. If empty, then the default payment method
     *     name will not be set.
     * @param signature The signature of the app. The SHA256 hash of this signature is called
     *     "fingerprint" and should be present in the app's web app manifest. If null, then this app
     *     will not have package info. If empty, then this app will not have any signatures.
     */
    public void installPaymentApp(
            CharSequence label,
            String packageName,
            String defaultPaymentMethodName,
            String signature) {
        installPaymentApp(
                label,
                packageName,
                defaultPaymentMethodName,
                /* supportedDelegations= */ null,
                signature);
    }

    /**
     * Simulates an installed payment app.
     *
     * @param label The user visible name of the app.
     * @param packageName The identifying package name.
     * @param defaultPaymentMethodName The name of the default payment method name for this app. If
     *     empty, then the default payment method name will not be set.
     * @param supportedDelegations The delegations that the app can support. If both
     *     supportedDelegations and defaultPaymentMethodName null, then this app will not have
     *     metadata.
     * @param signature The signature of the app. The SHA256 hash of this signature is called
     *     "fingerprint" and should be present in the app's web app manifest. If null, then this app
     *     will not have package info. If empty, then this app will not have any signatures.
     */
    public void installPaymentApp(
            CharSequence label,
            String packageName,
            String defaultPaymentMethodName,
            String[] supportedDelegations,
            String signature) {
        ResolveInfo paymentApp = new ResolveInfo();
        paymentApp.activityInfo = new ActivityInfo();
        paymentApp.activityInfo.packageName = packageName;
        paymentApp.activityInfo.name = packageName + ".WebPaymentActivity";
        paymentApp.activityInfo.applicationInfo = new ApplicationInfo();
        if (defaultPaymentMethodName != null || supportedDelegations != null) {
            Bundle metaData = new Bundle();
            if (!defaultPaymentMethodName.isEmpty()) {
                metaData.putString(
                        AndroidPaymentAppFinder.META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME,
                        defaultPaymentMethodName);
            }
            if (supportedDelegations != null && supportedDelegations.length > 0) {
                metaData.putInt(
                        AndroidPaymentAppFinder.META_DATA_NAME_OF_SUPPORTED_DELEGATIONS,
                        SUPPORTED_DELEGATIONS_STRING_ARRAY_RESOURCE_ID);
                List<String[]> resources = Arrays.asList(new String[RESOURCES_SIZE][]);
                resources.set(
                        SUPPORTED_DELEGATIONS_STRING_ARRAY_RESOURCE_ID - 1, supportedDelegations);
                mResources.put(paymentApp.activityInfo.applicationInfo, resources);
            }
            paymentApp.activityInfo.metaData = metaData;
        }
        mActivities.add(paymentApp);

        if (signature != null) {
            PackageInfo packageInfo = new PackageInfo();
            packageInfo.packageName = packageName;
            packageInfo.versionCode = 10;
            if (signature.isEmpty()) {
                packageInfo.signatures = new Signature[0];
            } else {
                packageInfo.signatures = new Signature[1];
                packageInfo.signatures[0] = new Signature(signature);
            }
            mPackages.put(packageName, packageInfo);
        }

        mLabels.put(paymentApp, label);
    }

    /**
     * Simulates an IS_READY_TO_PAY service in a payment app.
     *
     * @param packageName The identifying package name.
     */
    public void addIsReadyToPayService(String packageName) {
        ResolveInfo service = new ResolveInfo();
        service.serviceInfo = new ServiceInfo();
        service.serviceInfo.packageName = packageName;
        service.serviceInfo.name = packageName + ".IsReadyToPayService";
        mServices.add(service);
    }

    /**
     * Simulates META_DATA_NAME_OF_PAYMENT_METHOD_NAMES metadata in a payment app.
     *
     * @param packageName The name of the simulated package that contains the metadata.
     * @param metadata The metadata to simulate.
     */
    public void setStringArrayMetaData(String packageName, String[] metadata) {
        for (int i = 0; i < mActivities.size(); i++) {
            ResolveInfo paymentApp = mActivities.get(i);
            if (paymentApp.activityInfo.packageName.equals(packageName)) {
                paymentApp.activityInfo.metaData.putInt(
                        AndroidPaymentAppFinder.META_DATA_NAME_OF_PAYMENT_METHOD_NAMES,
                        PAYMENT_METHOD_NAMES_STRING_ARRAY_RESOURCE_ID);
                List<String[]> resources;
                if (mResources.containsKey(paymentApp.activityInfo.applicationInfo)) {
                    resources = mResources.get(paymentApp.activityInfo.applicationInfo);
                    mResources.remove(paymentApp.activityInfo.applicationInfo);
                } else {
                    resources = Arrays.asList(new String[RESOURCES_SIZE][]);
                }
                resources.set(PAYMENT_METHOD_NAMES_STRING_ARRAY_RESOURCE_ID - 1, metadata);
                mResources.put(paymentApp.activityInfo.applicationInfo, resources);
                return;
            }
        }
        assert false : packageName + " package not found";
    }

    /** Resets the package manager to the state of no installed apps. */
    public void reset() {
        mActivities.clear();
        mPackages.clear();
        mLabels.clear();
    }

    @Override
    public List<ResolveInfo> getActivitiesThatCanRespondToIntentWithMetaData(Intent intent) {
        return mActivities;
    }

    @Override
    public List<ResolveInfo> getActivitiesThatCanRespondToIntent(Intent intent) {
        return getActivitiesThatCanRespondToIntentWithMetaData(intent);
    }

    @Override
    public List<ResolveInfo> getServicesThatCanRespondToIntent(Intent intent) {
        return mServices;
    }

    @Override
    public PackageInfo getPackageInfoWithSignatures(String packageName) {
        return mPackages.get(packageName);
    }

    @Override
    public PackageInfo getPackageInfoWithSignatures(int uid) {
        return mPackages.get(mInvokedAppPackageName);
    }

    @Override
    public CharSequence getAppLabel(ResolveInfo resolveInfo) {
        return mLabels.get(resolveInfo);
    }

    @Override
    public Drawable getAppIcon(ResolveInfo resolveInfo) {
        return null;
    }

    @Override
    @Nullable
    public String[] getStringArrayResourceForApplication(
            ApplicationInfo applicationInfo, int resourceId) {
        assert resourceId > 0 && resourceId <= RESOURCES_SIZE;
        return mResources.get(applicationInfo).get(resourceId - 1);
    }

    /**
     * Sets the package name of the invoked payment app.
     *
     * @param packageName The package name of the invoked payment app.
     */
    public void setInvokedAppPackageName(String packageName) {
        assert mPackages.containsKey(packageName);
        mInvokedAppPackageName = packageName;
    }

    @Override
    public @Nullable String getInstallerPackage(String packageName) {
        return mMockInstallerPackageMap.get(packageName);
    }

    /**
     * Mock the installer of a specified package.
     *
     * @param packageName The package name that is intended to mock a installer for, not allowed to
     *     be null.
     * @param installerPackageName The package name intended to be set as the installer of the
     *     specified package.
     */
    public void mockInstallerForPackage(String packageName, @Nullable String installerPackageName) {
        assert packageName != null;
        mMockInstallerPackageMap.put(packageName, installerPackageName);
    }
}