/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.webkit;

import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.ChildZygoteProcess;
import android.os.Process;
import android.os.ZygoteProcess;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;

/** @hide */
public class WebViewZygote {
    private static final String LOGTAG = "WebViewZygote";

    /**
     * Lock object that protects all other static members.
     */
    private static final Object sLock = new Object();

    /**
     * Instance that maintains the socket connection to the zygote. This is {@code null} if the
     * zygote is not running or is not connected.
     */
    @GuardedBy("sLock")
    private static ChildZygoteProcess sZygote;

    /**
     * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
     */
    @GuardedBy("sLock")
    private static PackageInfo sPackage;

    /**
     * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
     * will not be started.
     */
    @GuardedBy("sLock")
    private static boolean sMultiprocessEnabled = false;

    public static ZygoteProcess getProcess() {
        synchronized (sLock) {
            if (sZygote != null) return sZygote;

            connectToZygoteIfNeededLocked();
            return sZygote;
        }
    }

    public static String getPackageName() {
        synchronized (sLock) {
            return sPackage.packageName;
        }
    }

    public static boolean isMultiprocessEnabled() {
        synchronized (sLock) {
            return sMultiprocessEnabled && sPackage != null;
        }
    }

    public static void setMultiprocessEnabled(boolean enabled) {
        synchronized (sLock) {
            sMultiprocessEnabled = enabled;

            // When multi-process is disabled, kill the zygote. When it is enabled,
            // the zygote will be started when it is first needed in getProcess().
            if (!enabled) {
                stopZygoteLocked();
            }
        }
    }

    static void onWebViewProviderChanged(PackageInfo packageInfo) {
        synchronized (sLock) {
            sPackage = packageInfo;

            // If multi-process is not enabled, then do not start the zygote service.
            if (!sMultiprocessEnabled) {
                return;
            }

            stopZygoteLocked();
        }
    }

    @GuardedBy("sLock")
    private static void stopZygoteLocked() {
        if (sZygote != null) {
            // Close the connection and kill the zygote process. This will not cause
            // child processes to be killed by itself. But if this is called in response to
            // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater
            // will kill all processes that depend on the WebView package.
            sZygote.close();
            Process.killProcess(sZygote.getPid());
            sZygote = null;
        }
    }

    @GuardedBy("sLock")
    private static void connectToZygoteIfNeededLocked() {
        if (sZygote != null) {
            return;
        }

        if (sPackage == null) {
            Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
            return;
        }

        try {
            String abi = sPackage.applicationInfo.primaryCpuAbi;
            int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
                    sPackage.applicationInfo, null);
            sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                    "com.android.internal.os.WebViewZygoteInit",
                    "webview_zygote",
                    Process.WEBVIEW_ZYGOTE_UID,
                    Process.WEBVIEW_ZYGOTE_UID,
                    null,  // gids
                    runtimeFlags,
                    "webview_zygote",  // seInfo
                    abi,  // abi
                    TextUtils.join(",", Build.SUPPORTED_ABIS),
                    null, // instructionSet
                    Process.FIRST_ISOLATED_UID,
                    Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly
            ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
            sZygote.preloadApp(sPackage.applicationInfo, abi);
        } catch (Exception e) {
            Log.e(LOGTAG, "Error connecting to webview zygote", e);
            stopZygoteLocked();
        }
    }
}
