chromium/content/public/android/java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.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.content.app;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.Surface;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.UnguessableToken;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.memory.MemoryPressureUma;
import org.chromium.base.process_launcher.ChildProcessServiceDelegate;
import org.chromium.content.browser.ChildProcessCreationParamsImpl;
import org.chromium.content.browser.ContentChildProcessConstants;
import org.chromium.content.common.IGpuProcessCallback;
import org.chromium.content.common.SurfaceWrapper;
import org.chromium.content_public.common.ContentProcessInfo;

import java.util.List;

/**
 * This implementation of {@link ChildProcessServiceDelegate} loads the native library, provides
 * access to view surfaces.
 */
@JNINamespace("content")
public class ContentChildProcessServiceDelegate implements ChildProcessServiceDelegate {
    private static final String TAG = "ContentCPSDelegate";

    // The binder box passed to us by the browser. May be null.
    private IBinder mBinderBox;

    private IGpuProcessCallback mGpuCallback;

    private int mCpuCount;
    private long mCpuFeatures;

    private SparseArray<String> mFdsIdsToKeys;

    public ContentChildProcessServiceDelegate() {
        KillChildUncaughtExceptionHandler.maybeInstallHandler();
    }

    @Override
    public void onServiceCreated() {
        ContentProcessInfo.setInChildProcess(true);
    }

    @Override
    public void onServiceBound(Intent intent) {
        LibraryLoader.getInstance().getMediator().takeLoadAddressFromBundle(intent.getExtras());
        LibraryLoader.getInstance()
                .setLibraryProcessType(
                        ChildProcessCreationParamsImpl.getLibraryProcessType(intent.getExtras()));
    }

    @Override
    public void onConnectionSetup(
            Bundle connectionBundle, List<IBinder> clientInterfaces, IBinder binderBox) {
        mBinderBox = binderBox;
        mGpuCallback =
                clientInterfaces != null && !clientInterfaces.isEmpty()
                        ? IGpuProcessCallback.Stub.asInterface(clientInterfaces.get(0))
                        : null;

        mCpuCount = connectionBundle.getInt(ContentChildProcessConstants.EXTRA_CPU_COUNT);
        mCpuFeatures = connectionBundle.getLong(ContentChildProcessConstants.EXTRA_CPU_FEATURES);
        assert mCpuCount > 0;

        LibraryLoader.getInstance().getMediator().takeSharedRelrosFromBundle(connectionBundle);
    }

    @Override
    public void preloadNativeLibrary(String packageName) {
        // This function can be called before command line is set. That is fine because
        // preloading explicitly doesn't run any Chromium code, see NativeLibraryPreloader
        // for more info.
        LibraryLoader.getInstance().preloadNowOverridePackageName(packageName);
    }

    @Override
    public void loadNativeLibrary(Context hostContext) {
        if (LibraryLoader.getInstance().isLoadedByZygote()) {
            initializeLibrary();
            return;
        }

        LibraryLoader libraryLoader = LibraryLoader.getInstance();
        libraryLoader.getMediator().initInChildProcess();
        libraryLoader.loadNowOverrideApplicationContext(hostContext);
        initializeLibrary();
    }

    private void initializeLibrary() {
        LibraryLoader.getInstance().initialize();

        // Now that the library is loaded, get the FD map,
        // TODO(jcivelli): can this be done in onBeforeMain? We would have to mode onBeforeMain
        // so it's called before FDs are registered.
        ContentChildProcessServiceDelegateJni.get()
                .retrieveFileDescriptorsIdsToKeys(ContentChildProcessServiceDelegate.this);
    }

    @Override
    public void consumeRelroBundle(Bundle bundle) {
        // Does not block, but may jank slightly. If the library has not been loaded yet, the bundle
        // will be unpacked and saved for the future. If the library is loaded, the RELRO region
        // will be replaced, which involves mmap(2) of shared memory and memcpy+memcmp of a few MB.
        LibraryLoader.getInstance().getMediator().takeSharedRelrosFromBundle(bundle);
    }

    @Override
    public SparseArray<String> getFileDescriptorsIdsToKeys() {
        assert mFdsIdsToKeys != null;
        return mFdsIdsToKeys;
    }

    @Override
    public void onBeforeMain() {
        ContentChildProcessServiceDelegateJni.get()
                .initChildProcess(ContentChildProcessServiceDelegate.this, mCpuCount, mCpuFeatures);
        ThreadUtils.getUiThreadHandler()
                .post(
                        () -> {
                            ContentChildProcessServiceDelegateJni.get()
                                    .initMemoryPressureListener();
                            MemoryPressureUma.initializeForChildService();
                        });
    }

    @Override
    public void runMain() {
        ContentMain.setBindersFromParent(mBinderBox);
        ContentMain.start(false);
    }

    @CalledByNative
    private void setFileDescriptorsIdsToKeys(int[] ids, String[] keys) {
        assert ids.length == keys.length;
        assert mFdsIdsToKeys == null;
        mFdsIdsToKeys = new SparseArray<>();
        for (int i = 0; i < ids.length; ++i) {
            mFdsIdsToKeys.put(ids[i], keys[i]);
        }
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void forwardSurfaceForSurfaceRequest(
            @JniType("base::UnguessableToken") UnguessableToken requestToken, Surface surface) {
        if (mGpuCallback == null) {
            Log.e(TAG, "No callback interface has been provided.");
            return;
        }

        try {
            mGpuCallback.forwardSurfaceForSurfaceRequest(requestToken, surface);
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to call forwardSurfaceForSurfaceRequest: %s", e);
            return;
        } finally {
            surface.release();
        }
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private SurfaceWrapper getViewSurface(int surfaceId) {
        if (mGpuCallback == null) {
            Log.e(TAG, "No callback interface has been provided.");
            return null;
        }

        try {
            SurfaceWrapper wrapper = mGpuCallback.getViewSurface(surfaceId);
            return wrapper;
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to call getViewSurface: %s", e);
            return null;
        }
    }

    @NativeMethods
    interface Natives {
        /**
         * Initializes the native parts of the service.
         *
         * @param cpuCount The number of CPUs.
         * @param cpuFeatures The CPU features.
         */
        void initChildProcess(
                ContentChildProcessServiceDelegate caller, int cpuCount, long cpuFeatures);

        /**
         * Initializes the MemoryPressureListener on the same thread callbacks will be
         * received on.
         */
        void initMemoryPressureListener();

        // Retrieves the FD IDs to keys map and set it by calling setFileDescriptorsIdsToKeys().
        void retrieveFileDescriptorsIdsToKeys(ContentChildProcessServiceDelegate caller);
    }
}