chromium/android_webview/javatests/src/org/chromium/android_webview/test/util/ManifestMetadataMockApplicationContext.java

// Copyright 2022 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.android_webview.test.util;

import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Bundle;

import org.mockito.Mockito;

import org.chromium.base.test.util.ApplicationContextWrapper;
import org.chromium.base.test.util.PackageManagerWrapper;

import java.util.HashMap;
import java.util.Map;

/**
 * Mock ApplicationContext that allows you to inject meta-data for a service
 * and resources.
 */
public class ManifestMetadataMockApplicationContext extends ApplicationContextWrapper {
    Map<String, Bundle> mApplicationMetadata = new HashMap<>();
    Map<ComponentName, Bundle> mServiceMetadata = new HashMap<>();
    Map<Integer, String[]> mStringArrayResources = new HashMap<>();

    /**
     * Construct a new Mock context, wrapping the passed (real) applicationContexxt
     * @param applicationContext The real context to wrap
     */
    public ManifestMetadataMockApplicationContext(final Context applicationContext) {
        super(applicationContext);
    }

    /**
     * Put a new metadata bundle into the context for the application package.
     *
     * The value will be returned as {@code getPackageManager().getApplicationInfo().metaData}
     * @param packageName Package name of the application
     * @param metadata Metadata to return
     */
    public void putServiceMetadata(String packageName, Bundle metadata) {
        mApplicationMetadata.put(packageName, metadata);
    }

    /**
     * Put a new metadata bundle into the context for the given service component.
     *
     * The value will be returned as {@code getPackageManager().getServiceInfo().metaData}
     * @param name ComponentName of the service
     * @param metadata Metadata to return
     */
    public void putServiceMetadata(ComponentName name, Bundle metadata) {
        mServiceMetadata.put(name, metadata);
    }

    /**
     * Add a new StringArray resource with the given ID.
     *
     * Values will be returned through {@code getResources().getStringArray()}
     *
     * @param id Resource ID
     * @param value String array value to return.
     */
    public void putStringArrayResource(int id, String[] value) {
        mStringArrayResources.put(id, value);
    }

    @Override
    public Resources getResources() {
        // The resources class is not built to be easily wrapped.
        // Create a wrapping spy instead.
        Resources resourcesSpy = Mockito.spy(super.getResources());
        // Use the "doAnswer-when" ordering since the real method call throws an exception
        // which breaks the more fluent "when-then" call pattern.
        Mockito.doAnswer(
                        invocation -> {
                            Integer key = invocation.getArgument(0);
                            if (mStringArrayResources.containsKey(key)) {
                                return mStringArrayResources.get(key);
                            }
                            return invocation.callRealMethod();
                        })
                .when(resourcesSpy)
                .getStringArray(Mockito.anyInt());
        return resourcesSpy;
    }

    @Override
    public PackageManager getPackageManager() {
        return new PackageManagerWrapper(super.getPackageManager()) {
            @Override
            public ApplicationInfo getApplicationInfo(String packageName, int flags)
                    throws NameNotFoundException {
                var applicationInfo = super.getApplicationInfo(packageName, flags);
                if (mApplicationMetadata.containsKey(packageName)) {
                    var injectedBundle = mApplicationMetadata.get(packageName);
                    if (applicationInfo.metaData != null) {
                        applicationInfo.metaData.putAll(injectedBundle);
                    } else {
                        applicationInfo.metaData = injectedBundle;
                    }
                }
                return applicationInfo;
            }

            @Override
            public ServiceInfo getServiceInfo(ComponentName component, int flags)
                    throws NameNotFoundException {
                if (mServiceMetadata.containsKey(component)) {
                    ServiceInfo serviceInfo = new ServiceInfo();
                    serviceInfo.metaData = mServiceMetadata.get(component);
                    return serviceInfo;
                }
                return super.getServiceInfo(component, flags);
            }
        };
    }
}