/*
 * Copyright (C) 2014 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 com.android.internal.os;

import static android.system.OsConstants.O_CLOEXEC;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProcessInfo;
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Build;
import android.os.FactoryTest;
import android.os.IVold;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.provider.DeviceConfig;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import com.android.internal.compat.IPlatformCompat;
import com.android.internal.net.NetworkUtilsInternal;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.ZygoteHooks;

import libcore.io.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;

/** @hide */
public final class Zygote {
    /*
    * Bit values for "runtimeFlags" argument.  The definitions are duplicated
    * in the native code.
    */

    /** enable debugging over JDWP */
    public static final int DEBUG_ENABLE_JDWP   = 1;
    /** enable JNI checks */
    public static final int DEBUG_ENABLE_CHECKJNI   = 1 << 1;
    /** enable Java programming language "assert" statements */
    public static final int DEBUG_ENABLE_ASSERT     = 1 << 2;
    /** disable the AOT compiler and JIT */
    public static final int DEBUG_ENABLE_SAFEMODE   = 1 << 3;
    /** Enable logging of third-party JNI activity. */
    public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
    /** Force generation of native debugging information. */
    public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
    /** Always use JIT-ed code. */
    public static final int DEBUG_ALWAYS_JIT = 1 << 6;
    /** Make the code native debuggable by turning off some optimizations. */
    public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
    /** Make the code Java debuggable by turning off some optimizations. */
    public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;

    /** Turn off the verifier. */
    public static final int DISABLE_VERIFIER = 1 << 9;
    /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
    public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
    /** Force generation of native debugging information for backtraces. */
    public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11;
    /**
     * Hidden API access restrictions. This is a mask for bits representing the API enforcement
     * policy, defined by {@code @ApplicationInfo.HiddenApiEnforcementPolicy}.
     */
    public static final int API_ENFORCEMENT_POLICY_MASK = (1 << 12) | (1 << 13);
    /**
     * Bit shift for use with {@link #API_ENFORCEMENT_POLICY_MASK}.
     *
     * (flags & API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT gives
     * {@link ApplicationInfo.HiddenApiEnforcementPolicy} values.
     */
    public static final int API_ENFORCEMENT_POLICY_SHIFT =
            Integer.numberOfTrailingZeros(API_ENFORCEMENT_POLICY_MASK);
    /**
     * Enable system server ART profiling.
     */
    public static final int PROFILE_SYSTEM_SERVER = 1 << 14;

    /**
     * Enable profiling from shell.
     */
    public static final int PROFILE_FROM_SHELL = 1 << 15;

    /*
     * Enable using the ART app image startup cache
     */
    public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;

    /**
     * When set, application specified signal handlers are not chained (i.e, ignored)
     * by the runtime.
     *
     * Used for debugging only. Usage: set debug.ignoreappsignalhandler to 1.
     */
    public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;

    /**
     * Disable runtime access to {@link android.annotation.TestApi} annotated members.
     *
     * <p>This only takes effect if Hidden API access restrictions are enabled as well.
     */
    public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;

    public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);

    public static final int MEMORY_TAG_LEVEL_NONE = 0;

    /**
     * Enable pointer tagging in this process.
     * Tags are checked during memory deallocation, but not on access.
     * TBI stands for Top-Byte-Ignore, an ARM CPU feature.
     * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging}
     */
    public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19;

    /**
     * Enable asynchronous memory tag checks in this process.
     */
    public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19;

    /**
     * Enable synchronous memory tag checks in this process.
     */
    public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19;

    /**
     * A two-bit field for GWP-ASan level of this process. See the possible values below.
     */
    public static final int GWP_ASAN_LEVEL_MASK = (1 << 21) | (1 << 22);

    /**
     * Disable GWP-ASan in this process.
     * GWP-ASan is a low-overhead memory bug detector using guard pages on a small
     * subset of heap allocations.
     */
    public static final int GWP_ASAN_LEVEL_NEVER = 0 << 21;

    /**
     * Enable GWP-ASan in this process with a small sampling rate.
     * With approx. 1% chance GWP-ASan will be activated and apply its protection
     * to a small subset of heap allocations.
     * Otherwise (~99% chance) this process is unaffected.
     */
    public static final int GWP_ASAN_LEVEL_LOTTERY = 1 << 21;

    /**
     * Always enable GWP-ASan in this process.
     * GWP-ASan is activated unconditionally (but still, only a small subset of
     * allocations is protected).
     */
    public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21;

    /**
     * GWP-ASan's `gwpAsanMode` manifest flag was unspecified. Currently, this
     * means GWP_ASAN_LEVEL_LOTTERY for system apps, and GWP_ASAN_LEVEL_NONE for
     * non-system apps.
     */
    public static final int GWP_ASAN_LEVEL_DEFAULT = 3 << 21;

    /** Enable automatic zero-initialization of native heap memory allocations. */
    public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;

    /**
     * Enable profiling from system services. This loads profiling related plugins in ART.
     */
    public static final int PROFILEABLE = 1 << 24;

    /** No external storage should be mounted. */
    public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
    /** Default external storage should be mounted. */
    public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
    /**
     * Mount mode for package installers which should give them access to
     * all obb dirs in addition to their package sandboxes
     */
    public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
    /** The lower file system should be bind mounted directly on external storage */
    public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;

    /** Use the regular scoped storage filesystem, but Android/ should be writable.
     * Used to support the applications hosting DownloadManager and the MTP server.
     */
    public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE;

    /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
    static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;

    /** Make the new process have top application priority. */
    public static final String START_AS_TOP_APP_ARG = "--is-top-app";

    /** List of packages with the same uid, and its app data info: volume uuid and inode. */
    public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";

    /** List of allowlisted packages and its app data info: volume uuid and inode. */
    public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map";

    /** Bind mount app storage dirs to lower fs not via fuse */
    public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";

    /** Bind mount app storage dirs to lower fs not via fuse */
    public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";

    /**
     * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
     * in the abstract socket namespace. This socket name is what the new child zygote
     * should listen for connections on.
     */
    public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";

    /**
     * An extraArg passed when a zygote process is forking a child-zygote, specifying the
     * requested ABI for the child Zygote.
     */
    public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list=";

    /**
     * An extraArg passed when a zygote process is forking a child-zygote, specifying the
     * start of the UID range the children of the Zygote may setuid()/setgid() to. This
     * will be enforced with a seccomp filter.
     */
    public static final String CHILD_ZYGOTE_UID_RANGE_START = "--uid-range-start=";

    /**
     * An extraArg passed when a zygote process is forking a child-zygote, specifying the
     * end of the UID range the children of the Zygote may setuid()/setgid() to. This
     * will be enforced with a seccomp filter.
     */
    public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";

    private static final String TAG = "Zygote";

    /** Prefix prepended to socket names created by init */
    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";

    /**
     * The duration to wait before re-checking Zygote related system properties.
     *
     * One minute in milliseconds.
     */
    public static final long PROPERTY_CHECK_INTERVAL = 60000;

    /**
     * @hide for internal use only
     */
    public static final int SOCKET_BUFFER_SIZE = 256;

    /**
     * @hide for internal use only
     */
    private static final int PRIORITY_MAX = -20;

    /** a prototype instance for a future List.toArray() */
    static final int[][] INT_ARRAY_2D = new int[0][0];

    /**
     * @hide for internal use only.
     */
    public static final String PRIMARY_SOCKET_NAME = "zygote";

    /**
     * @hide for internal use only.
     */
    public static final String SECONDARY_SOCKET_NAME = "zygote_secondary";

    /**
     * @hide for internal use only
     */
    public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

    /**
     * @hide for internal use only
     */
    public static final String USAP_POOL_SECONDARY_SOCKET_NAME = "usap_pool_secondary";

    private Zygote() {}

    private static boolean containsInetGid(int[] gids) {
        for (int i = 0; i < gids.length; i++) {
            if (gids[i] == android.os.Process.INET_GID) return true;
        }
        return false;
    }

    /**
     * Forks a new VM instance.  The current VM must have been started
     * with the -Xzygote flag. <b>NOTE: new instance keeps all
     * root capabilities. The new process is expected to call capset()</b>.
     *
     * @param uid the UNIX uid that the new process should setuid() to after
     * fork()ing and and before spawning any threads.
     * @param gid the UNIX gid that the new process should setgid() to after
     * fork()ing and and before spawning any threads.
     * @param gids null-ok; a list of UNIX gids that the new process should
     * setgroups() to after fork and before spawning any threads.
     * @param runtimeFlags bit flags that enable ART features.
     * @param rlimits null-ok an array of rlimit tuples, with the second
     * dimension having a length of 3 and representing
     * (resource, rlim_cur, rlim_max). These are set via the posix
     * setrlimit(2) call.
     * @param seInfo null-ok a string specifying SELinux information for
     * the new process.
     * @param niceName null-ok a string specifying the process name.
     * @param fdsToClose an array of ints, holding one or more POSIX
     * file descriptor numbers that are to be closed by the child
     * (and replaced by /dev/null) after forking.  An integer value
     * of -1 in any entry in the array means "ignore this one".
     * @param fdsToIgnore null-ok an array of ints, either null or holding
     * one or more POSIX file descriptor numbers that are to be ignored
     * in the file descriptor table check.
     * @param startChildZygote if true, the new child process will itself be a
     * new zygote process.
     * @param instructionSet null-ok the instruction set to use.
     * @param appDataDir null-ok the data directory of the app.
     * @param isTopApp true if the process is for top (high priority) application.
     * @param pkgDataInfoList A list that stores related packages and its app data
     * info: volume uuid and inode.
     * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
     * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
     *
     * @return 0 if this is the child, pid of the child
     * if this is the parent, or -1 on error.
     */
    static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
            int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
            boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList,
            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
        ZygoteHooks.preFork();

        int pid = nativeForkAndSpecialize(
                uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
                pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs,
                bindMountAppStorageDirs);
        if (pid == 0) {
            // Note that this event ends at the end of handleChildProc,
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");

            // If no GIDs were specified, don't make any permissions changes based on groups.
            if (gids != null && gids.length > 0) {
                NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
            }
        }

        // Set the Java Language thread priority to the default value for new apps.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

        ZygoteHooks.postForkCommon();
        return pid;
    }

    private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
            int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
            String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
            String[] allowlistedDataInfoList, boolean bindMountAppDataDirs,
            boolean bindMountAppStorageDirs);

    /**
     * Specialize an unspecialized app process.  The current VM must have been started
     * with the -Xzygote flag.
     *
     * @param uid  The UNIX uid that the new process should setuid() to before spawning any threads
     * @param gid  The UNIX gid that the new process should setgid() to before spawning any threads
     * @param gids null-ok;  A list of UNIX gids that the new process should
     * setgroups() to before spawning any threads
     * @param runtimeFlags  Bit flags that enable ART features
     * @param rlimits null-ok  An array of rlimit tuples, with the second
     * dimension having a length of 3 and representing
     * (resource, rlim_cur, rlim_max). These are set via the posix
     * setrlimit(2) call.
     * @param seInfo null-ok  A string specifying SELinux information for
     * the new process.
     * @param niceName null-ok  A string specifying the process name.
     * @param startChildZygote  If true, the new child process will itself be a
     * new zygote process.
     * @param instructionSet null-ok  The instruction set to use.
     * @param appDataDir null-ok  The data directory of the app.
     * @param isTopApp  True if the process is for top (high priority) application.
     * @param pkgDataInfoList A list that stores related packages and its app data
     * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
     * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
     * app_b_ce_inode, ...];
     * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
     * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
     */
    private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, int mountExternal, String seInfo, String niceName,
            boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
            String[] pkgDataInfoList, String[] allowlistedDataInfoList,
            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
        nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
                pkgDataInfoList, allowlistedDataInfoList,
                bindMountAppDataDirs, bindMountAppStorageDirs);

        // Note that this event ends at the end of handleChildProc.
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");

        if (gids != null && gids.length > 0) {
            NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
        }

        // Set the Java Language thread priority to the default value for new apps.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

        /*
         * This is called here (instead of after the fork but before the specialize) to maintain
         * consistancy with the code paths for forkAndSpecialize.
         *
         * TODO (chriswailes): Look into moving this to immediately after the fork.
         */
        ZygoteHooks.postForkCommon();
    }

    private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
            boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
            String[] pkgDataInfoList, String[] allowlistedDataInfoList,
            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);

    /**
     * Called to do any initialization before starting an application.
     */
    static native void nativePreApplicationInit();

    /**
     * Special method to start the system server process. In addition to the
     * common actions performed in forkAndSpecialize, the pid of the child
     * process is recorded such that the death of the child process will cause
     * zygote to exit.
     *
     * @param uid the UNIX uid that the new process should setuid() to after
     * fork()ing and and before spawning any threads.
     * @param gid the UNIX gid that the new process should setgid() to after
     * fork()ing and and before spawning any threads.
     * @param gids null-ok; a list of UNIX gids that the new process should
     * setgroups() to after fork and before spawning any threads.
     * @param runtimeFlags bit flags that enable ART features.
     * @param rlimits null-ok an array of rlimit tuples, with the second
     * dimension having a length of 3 and representing
     * (resource, rlim_cur, rlim_max). These are set via the posix
     * setrlimit(2) call.
     * @param permittedCapabilities argument for setcap()
     * @param effectiveCapabilities argument for setcap()
     *
     * @return 0 if this is the child, pid of the child
     * if this is the parent, or -1 on error.
     */
    static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        ZygoteHooks.preFork();

        int pid = nativeForkSystemServer(
                uid, gid, gids, runtimeFlags, rlimits,
                permittedCapabilities, effectiveCapabilities);

        // Set the Java Language thread priority to the default value for new apps.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

        ZygoteHooks.postForkCommon();
        return pid;
    }

    private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);

    /**
     * Lets children of the zygote inherit open file descriptors to this path.
     */
    protected static native void nativeAllowFileAcrossFork(String path);

    /**
     * Lets children of the zygote inherit open file descriptors that belong to the
     * ApplicationInfo that is passed in.
     *
     * @param appInfo ApplicationInfo of the application
     */
    static void allowAppFilesAcrossFork(ApplicationInfo appInfo) {
        for (String path : appInfo.getAllApkPaths()) {
            Zygote.nativeAllowFileAcrossFork(path);
        }
    }

    /**
     * Scans file descriptors in /proc/self/fd/, stores their metadata from readlink(2)/stat(2) when
     * available. Saves this information in a global on native side, to be used by subsequent call
     * to allowFilesOpenedByPreload(). Fatally fails if the FDs are of unsupported type and are not
     * explicitly allowed. Ignores repeated invocations.
     *
     * Inspecting the FDs is more permissive than in forkAndSpecialize() because preload is invoked
     * earlier and hence needs to allow a few open sockets. The checks in forkAndSpecialize()
     * enforce that these sockets are closed when forking.
     */
    static void markOpenedFilesBeforePreload() {
        nativeMarkOpenedFilesBeforePreload();
    }

    private static native void nativeMarkOpenedFilesBeforePreload();

    /**
     * By scanning /proc/self/fd/ determines file descriptor numbers in this process opened since
     * the first call to markOpenedFilesBeforePreload(). These FDs are treated as 'owned' by the
     * custom preload of the App Zygote - the app is responsible for not sharing data with its other
     * processes using these FDs, including by lseek(2). File descriptor types and file names are
     * not checked. Changes in FDs recorded by markOpenedFilesBeforePreload() are not expected and
     * kill the current process.
     */
    static void allowFilesOpenedByPreload() {
        nativeAllowFilesOpenedByPreload();
    }

    private static native void nativeAllowFilesOpenedByPreload();

    /**
     * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
     * @param uidGidMin The smallest allowed uid/gid
     * @param uidGidMax The largest allowed uid/gid
     */
    native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax);

    /**
     * Initialize the native state of the Zygote.  This inclues
     *   - Fetching socket FDs from the environment
     *   - Initializing security properties
     *   - Unmounting storage as appropriate
     *   - Loading necessary performance profile information
     *
     * @param isPrimary  True if this is the zygote process, false if it is zygote_secondary
     */
    static void initNativeState(boolean isPrimary) {
        nativeInitNativeState(isPrimary);
    }

    protected static native void nativeInitNativeState(boolean isPrimary);

    /**
     * Returns the raw string value of a system property.
     *
     * Note that Device Config is not available without an application so SystemProperties is used
     * instead.
     *
     * TODO (chriswailes): Cache the system property location in native code and then write a JNI
     *                     function to fetch it.
     */
    public static String getConfigurationProperty(String propertyName, String defaultValue) {
        return SystemProperties.get(
                String.join(".",
                        "persist.device_config",
                        DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
                        propertyName),
                defaultValue);
    }

    static void emptyUsapPool() {
        nativeEmptyUsapPool();
    }

    private static native void nativeEmptyUsapPool();

    /**
     * Returns the value of a system property converted to a boolean using specific logic.
     *
     * Note that Device Config is not available without an application so SystemProperties is used
     * instead.
     *
     * @see SystemProperties#getBoolean
     *
     * TODO (chriswailes): Cache the system property location in native code and then write a JNI
     *                     function to fetch it.
     * TODO (chriswailes): Move into ZygoteConfig.java once the necessary CL lands (go/ag/6580627)
     */
    public static boolean getConfigurationPropertyBoolean(
            String propertyName, Boolean defaultValue) {
        return SystemProperties.getBoolean(
                String.join(".",
                        "persist.device_config",
                        DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
                        propertyName),
                defaultValue);
    }

    /**
     * @return Number of unspecialized app processes currently in the pool
     */
    static int getUsapPoolCount() {
        return nativeGetUsapPoolCount();
    }

    private static native int nativeGetUsapPoolCount();

    /**
     * @return The event FD used for communication between the signal handler and the ZygoteServer
     *         poll loop
     */
    static FileDescriptor getUsapPoolEventFD() {
        FileDescriptor fd = new FileDescriptor();
        fd.setInt$(nativeGetUsapPoolEventFD());

        return fd;
    }

    private static native int nativeGetUsapPoolEventFD();

    /**
     * Fork a new unspecialized app process from the zygote. Adds the Usap to the native
     * Usap table.
     *
     * @param usapPoolSocket  The server socket the USAP will call accept on
     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open.
     *         These are closed in the child.
     * @param isPriorityFork Raise the initial process priority level because this is on the
     *         critical path for application startup.
     * @return In the child process, this returns a Runnable that waits for specialization
     *         info to start an app process. In the sygote/parent process this returns null.
     */
    static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket,
                                       int[] sessionSocketRawFDs,
                                       boolean isPriorityFork) {
        FileDescriptor readFD;
        FileDescriptor writeFD;

        try {
            FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC);
            readFD = pipeFDs[0];
            writeFD = pipeFDs[1];
        } catch (ErrnoException errnoEx) {
            throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
        }

        int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(),
                                sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork);
        if (pid == 0) {
            IoUtils.closeQuietly(readFD);
            return childMain(null, usapPoolSocket, writeFD);
        } else if (pid == -1) {
            // Fork failed.
            return null;
        } else {
            // readFD will be closed by the native code. See removeUsapTableEntry();
            IoUtils.closeQuietly(writeFD);
            nativeAddUsapTableEntry(pid, readFD.getInt$());
            return null;
        }
    }

    private static native int nativeForkApp(int readPipeFD,
                                            int writePipeFD,
                                            int[] sessionSocketRawFDs,
                                            boolean argsKnown,
                                            boolean isPriorityFork);

    /**
     * Add an entry for a new Usap to the table maintained in native code.
     */
    @CriticalNative
    private static native void nativeAddUsapTableEntry(int pid, int readPipeFD);

    /**
     * Fork a new app process from the zygote. argBuffer contains a fork command that
     * request neither a child zygote, nor a wrapped process. Continue to accept connections
     * on the specified socket, use those to refill argBuffer, and continue to process
     * sufficiently simple fork requests. We presume that the only open file descriptors
     * requiring special treatment are the session socket embedded in argBuffer, and
     * zygoteSocket.
     * @param argBuffer containing initial command and the connected socket from which to
     *         read more
     * @param zygoteSocket socket from which to obtain new connections when current argBuffer
     *         one is disconnected
     * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different
     *               peer will cause us to return rather than perform the requested fork.
     * @param minUid Minimum Uid enforced for all but first fork request. The caller checks
     *               the Uid policy for the initial request.
     * @param firstNiceName name of first created process. Used for error reporting only.
     * @return A Runnable in each child process, null in the parent.
     * If this returns in then argBuffer still contains a command needing to be executed.
     */
    static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer,
                                             @NonNull FileDescriptor zygoteSocket,
                                             int expectedUid,
                                             int minUid,
                                             @Nullable String firstNiceName) {
        boolean in_child =
                argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName);
        if (in_child) {
            return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null);
        } else {
            return null;
        }
    }

    /**
     * Specialize the current process into one described by argBuffer or the command read from
     * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close
     * it. Used both for a specializing a USAP process, and for process creation without USAPs.
     * In both cases, we specialize the process after first returning to Java code.
     *
     * @param writePipe  The write end of the reporting pipe used to communicate with the poll loop
     *                   of the ZygoteServer.
     * @return A runnable oject representing the new application.
     */
    private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer,
                                      @Nullable LocalServerSocket usapPoolSocket,
                                      FileDescriptor writePipe) {
        final int pid = Process.myPid();

        DataOutputStream usapOutputStream = null;
        ZygoteArguments args = null;

        LocalSocket sessionSocket = null;
        if (argBuffer == null) {
            // Read arguments from usapPoolSocket instead.

            Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");

            // Change the priority to max before calling accept so we can respond to new
            // specialization requests as quickly as possible.  This will be reverted to the
            // default priority in the native specialization code.
            boostUsapPriority();

            while (true) {
                ZygoteCommandBuffer tmpArgBuffer = null;
                try {
                    sessionSocket = usapPoolSocket.accept();
                    // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
                    // This is safe from a race condition because the pool is only flushed after
                    // the SystemServer changes its internal state to stop using the USAP pool.
                    blockSigTerm();

                    usapOutputStream =
                            new DataOutputStream(sessionSocket.getOutputStream());
                    Credentials peerCredentials = sessionSocket.getPeerCredentials();
                    tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket);
                    args = ZygoteArguments.getInstance(tmpArgBuffer);
                    applyUidSecurityPolicy(args, peerCredentials);
                    // TODO (chriswailes): Should this only be run for debug builds?
                    validateUsapCommand(args);
                    break;
                } catch (Exception ex) {
                    Log.e("USAP", ex.getMessage());
                }
                // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
                unblockSigTerm();
                IoUtils.closeQuietly(sessionSocket);
                IoUtils.closeQuietly(tmpArgBuffer);
            }
        } else {
            // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
            blockSigTerm();
            try {
                args = ZygoteArguments.getInstance(argBuffer);
            } catch (Exception ex) {
                Log.e("AppStartup", ex.getMessage());
                throw new AssertionError("Failed to parse application start command", ex);
            }
            // peerCredentials were checked in parent.
        }
        if (args == null) {
            throw new AssertionError("Empty command line");
        }
        try {
            // SIGTERM is blocked here.  This prevents a USAP that is specializing from being
            // killed during a pool flush.

            applyDebuggerSystemProperty(args);

            int[][] rlimits = null;

            if (args.mRLimits != null) {
                rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
            }

            if (argBuffer == null) {
                // This must happen before the SELinux policy for this process is
                // changed when specializing.
                try {
                    // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
                    // Process.ProcessStartResult object.
                    usapOutputStream.writeInt(pid);
                } catch (IOException ioEx) {
                    Log.e("USAP", "Failed to write response to session socket: "
                            + ioEx.getMessage());
                    throw new RuntimeException(ioEx);
                } finally {
                    try {
                        // Since the raw FD is created by init and then loaded from an environment
                        // variable (as opposed to being created by the LocalSocketImpl itself),
                        // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See
                        // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd).
                        // Thus closing the LocalSocket does not suffice. See b/130309968 for more
                        // discussion.
                        FileDescriptor fd = usapPoolSocket.getFileDescriptor();
                        usapPoolSocket.close();
                        Os.close(fd);
                    } catch (ErrnoException | IOException ex) {
                        Log.e("USAP", "Failed to close USAP pool socket");
                        throw new RuntimeException(ex);
                    }
                }
            }

            if (writePipe != null) {
                try {
                    ByteArrayOutputStream buffer =
                            new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
                    DataOutputStream outputStream = new DataOutputStream(buffer);

                    // This is written as a long so that the USAP reporting pipe and USAP pool
                    // event FD handlers in ZygoteServer.runSelectLoop can be unified.  These two
                    // cases should both send/receive 8 bytes.
                    // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects
                    // a different format.
                    outputStream.writeLong(pid);
                    outputStream.flush();
                    Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
                } catch (Exception ex) {
                    Log.e("USAP",
                            String.format("Failed to write PID (%d) to pipe (%d): %s",
                                    pid, writePipe.getInt$(), ex.getMessage()));
                    throw new RuntimeException(ex);
                } finally {
                    IoUtils.closeQuietly(writePipe);
                }
            }

            specializeAppProcess(args.mUid, args.mGid, args.mGids,
                                 args.mRuntimeFlags, rlimits, args.mMountExternal,
                                 args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                 args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
                                 args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
                                 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);

            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

            return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
                                         args.mDisabledCompatChanges,
                                         args.mRemainingArgs,
                                         null /* classLoader */);
        } finally {
            // Unblock SIGTERM to restore the process to default behavior.
            unblockSigTerm();
        }
    }

    private static void blockSigTerm() {
        nativeBlockSigTerm();
    }

    private static native void nativeBlockSigTerm();

    private static void unblockSigTerm() {
        nativeUnblockSigTerm();
    }

    private static native void nativeUnblockSigTerm();

    private static void boostUsapPriority() {
        nativeBoostUsapPriority();
    }

    private static native void nativeBoostUsapPriority();

    static void setAppProcessName(ZygoteArguments args, String loggingTag) {
        if (args.mNiceName != null) {
            Process.setArgV0(args.mNiceName);
        } else if (args.mPackageName != null) {
            Process.setArgV0(args.mPackageName);
        } else {
            Log.w(loggingTag, "Unable to set package name.");
        }
    }

    private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: ";

    /**
     * Checks a set of zygote arguments to see if they can be handled by a USAP.  Throws an
     * exception if an invalid arugment is encountered.
     * @param args  The arguments to test
     */
    private static void validateUsapCommand(ZygoteArguments args) {
        if (args.mAbiListQuery) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--query-abi-list");
        } else if (args.mPidQuery) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid");
        } else if (args.mPreloadDefault) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default");
        } else if (args.mPreloadPackage != null) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package");
        } else if (args.mPreloadApp != null) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app");
        } else if (args.mStartChildZygote) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote");
        } else if (args.mApiDenylistExemptions != null) {
            throw new IllegalArgumentException(
                    USAP_ERROR_PREFIX + "--set-api-denylist-exemptions");
        } else if (args.mHiddenApiAccessLogSampleRate != -1) {
            throw new IllegalArgumentException(
                    USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
        } else if (args.mHiddenApiAccessStatslogSampleRate != -1) {
            throw new IllegalArgumentException(
                    USAP_ERROR_PREFIX + "--hidden-api-statslog-sampling-rate=");
        } else if (args.mInvokeWith != null) {
            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with");
        } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
            throw new ZygoteSecurityException("Client may not specify capabilities: "
                + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
                + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
        }
    }

    /**
     * @return  Raw file descriptors for the read-end of USAP reporting pipes.
     */
    static int[] getUsapPipeFDs() {
        return nativeGetUsapPipeFDs();
    }

    private static native int[] nativeGetUsapPipeFDs();

    /**
     * Remove the USAP table entry for the provided process ID.
     *
     * @param usapPID  Process ID of the entry to remove
     * @return True if the entry was removed; false if it doesn't exist
     */
    static boolean removeUsapTableEntry(int usapPID) {
        return nativeRemoveUsapTableEntry(usapPID);
    }

    @CriticalNative
    private static native boolean nativeRemoveUsapTableEntry(int usapPID);

    /**
     * Return the minimum child uid that the given peer is allowed to create.
     * uid 1000 (Process.SYSTEM_UID) may specify any uid &ge; 1000 in normal
     * operation. It may also specify any gid and setgroups() list it chooses.
     * In factory test mode, it may specify any UID.
     */
    static int minChildUid(Credentials peer) {
        if (peer.getUid() == Process.SYSTEM_UID
                && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) {
            /* In normal operation, SYSTEM_UID can only specify a restricted
             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
             */
            return Process.SYSTEM_UID;
        } else {
            return 0;
        }
    }

    /*
     * Adjust uid and gid arguments, ensuring that the security policy is satisfied.
     * @param args non-null; zygote spawner arguments
     * @param peer non-null; peer credentials
     * @throws ZygoteSecurityException Indicates a security issue when applying the UID based
     *  security policies
     */
    static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
            throws ZygoteSecurityException {

        if (args.mUidSpecified && (args.mUid < minChildUid(peer))) {
            throw new ZygoteSecurityException(
                    "System UID may not launch process with UID < "
                    + Process.SYSTEM_UID);
        }

        // If not otherwise specified, uid and gid are inherited from peer
        if (!args.mUidSpecified) {
            args.mUid = peer.getUid();
            args.mUidSpecified = true;
        }
        if (!args.mGidSpecified) {
            args.mGid = peer.getGid();
            args.mGidSpecified = true;
        }
    }

    /**
     * This will enable jdwp by default for all apps. It is OK to cache this property
     * because we expect to reboot the system whenever this property changes
     */
    private static final boolean ENABLE_JDWP = SystemProperties.get(
                          "persist.debug.dalvik.vm.jdwp.enabled").equals("1");

    /**
     * Applies debugger system properties to the zygote arguments.
     *
     * For eng builds all apps are debuggable. On userdebug and user builds
     * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are
     * debuggable. Otherwise, the debugger state is specified via the
     * "--enable-jdwp" flag in the spawn request.
     *
     * @param args non-null; zygote spawner args
     */
    static void applyDebuggerSystemProperty(ZygoteArguments args) {
        if (Build.IS_ENG || ENABLE_JDWP) {
            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
        }
    }

    /**
     * Applies zygote security policy.
     * Based on the credentials of the process issuing a zygote command:
     * <ol>
     * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
     * wrapper command.
     * <li> Any other uid may not specify any invoke-with argument.
     * </ul>
     *
     * @param args non-null; zygote spawner arguments
     * @param peer non-null; peer credentials
     * @throws ZygoteSecurityException Thrown when `--invoke-with` is specified for a non-debuggable
     *  application.
     */
    static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
            throws ZygoteSecurityException {
        int peerUid = peer.getUid();

        if (args.mInvokeWith != null && peerUid != 0
                && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
            throw new ZygoteSecurityException("Peer is permitted to specify an "
                + "explicit invoke-with wrapper command only for debuggable "
                + "applications.");
        }
    }

    /**
     * Gets the wrap property if set.
     *
     * @param appName the application name to check
     * @return value of wrap property or null if property not set or
     * null if app_name is null or null if app_name is empty
     */
    public static String getWrapProperty(String appName) {
        if (appName == null || appName.isEmpty()) {
            return null;
        }

        String propertyValue = SystemProperties.get("wrap." + appName);
        if (propertyValue != null && !propertyValue.isEmpty()) {
            return propertyValue;
        }
        return null;
    }

    /**
     * Applies invoke-with system properties to the zygote arguments.
     *
     * @param args non-null; zygote args
     */
    static void applyInvokeWithSystemProperty(ZygoteArguments args) {
        if (args.mInvokeWith == null) {
            args.mInvokeWith = getWrapProperty(args.mNiceName);
        }
    }

    /**
     * Creates a managed LocalServerSocket object using a file descriptor
     * created by an init.rc script.  The init scripts that specify the
     * sockets name can be found in system/core/rootdir.  The socket is bound
     * to the file system in the /dev/sockets/ directory, and the file
     * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
     * variable.
     */
    static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
        int fileDesc;
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;

        try {
            String env = System.getenv(fullSocketName);
            fileDesc = Integer.parseInt(env);
        } catch (RuntimeException ex) {
            throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
        }

        try {
            FileDescriptor fd = new FileDescriptor();
            fd.setInt$(fileDesc);
            return new LocalServerSocket(fd);
        } catch (IOException ex) {
            throw new RuntimeException(
                "Error building socket from file descriptor: " + fileDesc, ex);
        }
    }

    // This function is called from native code in com_android_internal_os_Zygote.cpp
    @SuppressWarnings("unused")
    private static void callPostForkSystemServerHooks(int runtimeFlags) {
        // SystemServer specific post fork hooks run before child post fork hooks.
        ZygoteHooks.postForkSystemServer(runtimeFlags);
    }

    // This function is called from native code in com_android_internal_os_Zygote.cpp
    @SuppressWarnings("unused")
    private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
            boolean isZygote, String instructionSet) {
        ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
    }

    /**
     * Executes "/system/bin/sh -c &lt;command&gt;" using the exec() system call.
     * This method throws a runtime exception if exec() failed, otherwise, this
     * method never returns.
     *
     * @param command The shell command to execute.
     */
    static void execShell(String command) {
        String[] args = { "/system/bin/sh", "-c", command };
        try {
            Os.execv(args[0], args);
        } catch (ErrnoException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Appends quotes shell arguments to the specified string builder.
     * The arguments are quoted using single-quotes, escaped if necessary,
     * prefixed with a space, and appended to the command.
     *
     * @param command A string builder for the shell command being constructed.
     * @param args An array of argument strings to be quoted and appended to the command.
     * @see #execShell(String)
     */
    static void appendQuotedShellArgs(StringBuilder command, String[] args) {
        for (String arg : args) {
            command.append(" '").append(arg.replace("'", "'\\''")).append("'");
        }
    }

    /**
     * Parse the given unsolicited zygote message as type SIGCHLD,
     * extract the payload information into the given output buffer.
     *
     * @param in The unsolicited zygote message to be parsed
     * @param length The number of bytes in the message
     * @param out The output buffer where the payload information will be placed
     * @return Number of elements being place into output buffer, or -1 if
     *         either the message is malformed or not the type as expected here.
     *
     * @hide
     */
    @FastNative
    public static native int nativeParseSigChld(byte[] in, int length, int[] out);

    /**
     * Returns whether the hardware supports memory tagging (ARM MTE).
     */
    public static native boolean nativeSupportsMemoryTagging();

    /**
     * Returns whether the kernel supports tagged pointers. Present in the
     * Android Common Kernel from 4.14 and up. By default, you should prefer
     * fully-feature Memory Tagging, rather than the static Tagged Pointers.
     */
    public static native boolean nativeSupportsTaggedPointers();

    /**
     * Returns the current native tagging level, as one of the
     * MEMORY_TAG_LEVEL_* constants. Returns zero if no tagging is present, or
     * we failed to determine the level.
     */
    public static native int nativeCurrentTaggingLevel();

    /**
     * Native heap allocations will now have a non-zero tag in the most significant byte.
     *
     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
     *     Pointers</a>
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.

    /**
     * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag
     * in the most significant byte.
     *
     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
     *     Pointers</a>
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
    private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677;

    /**
     * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an
     * effect on hardware supporting the ARM Memory Tagging Extension (MTE).
     */
    @ChangeId @Disabled
    private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.

    /**
     * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an
     * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both
     * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is
     * enabled in SYNC mode.
     */
    @ChangeId @Disabled
    private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.

    /** Enable automatic zero-initialization of native heap memory allocations. */
    @ChangeId @Disabled
    private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.

    /**
     * Enable sampled memory bug detection in the app.
     *
     * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
     */
    @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id.

    private static int memtagModeToZygoteMemtagLevel(int memtagMode) {
        switch (memtagMode) {
            case ApplicationInfo.MEMTAG_ASYNC:
                return MEMORY_TAG_LEVEL_ASYNC;
            case ApplicationInfo.MEMTAG_SYNC:
                return MEMORY_TAG_LEVEL_SYNC;
            default:
                return MEMORY_TAG_LEVEL_NONE;
        }
    }

    private static boolean isCompatChangeEnabled(
            long change,
            @NonNull ApplicationInfo info,
            @Nullable IPlatformCompat platformCompat,
            int enabledAfter) {
        try {
            if (platformCompat != null) return platformCompat.isChangeEnabled(change, info);
        } catch (RemoteException ignore) {
        }
        return enabledAfter > 0 && info.targetSdkVersion > enabledAfter;
    }

    // Returns the requested memory tagging level.
    private static int getRequestedMemtagLevel(
            @NonNull ApplicationInfo info,
            @Nullable ProcessInfo processInfo,
            @Nullable IPlatformCompat platformCompat) {
        String appOverride = SystemProperties.get("persist.arm64.memtag.app." + info.packageName);
        if ("sync".equals(appOverride)) {
            return MEMORY_TAG_LEVEL_SYNC;
        } else if ("async".equals(appOverride)) {
            return MEMORY_TAG_LEVEL_ASYNC;
        } else if ("off".equals(appOverride)) {
            return MEMORY_TAG_LEVEL_NONE;
        }

        // Look at the process attribute first.
        if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
            return memtagModeToZygoteMemtagLevel(processInfo.memtagMode);
        }

        // Then at the application attribute.
        if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
            return memtagModeToZygoteMemtagLevel(info.getMemtagMode());
        }

        if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) {
            return MEMORY_TAG_LEVEL_SYNC;
        }

        if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) {
            return MEMORY_TAG_LEVEL_ASYNC;
        }

        // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
        if (!info.allowsNativeHeapPointerTagging()) {
            return MEMORY_TAG_LEVEL_NONE;
        }

        String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
        if ("sync".equals(defaultLevel)) {
            return MEMORY_TAG_LEVEL_SYNC;
        } else if ("async".equals(defaultLevel)) {
            return MEMORY_TAG_LEVEL_ASYNC;
        }

        // Check to see that the compat feature for TBI is enabled.
        if (isCompatChangeEnabled(
                NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) {
            return MEMORY_TAG_LEVEL_TBI;
        }

        return MEMORY_TAG_LEVEL_NONE;
    }

    private static int decideTaggingLevel(
            @NonNull ApplicationInfo info,
            @Nullable ProcessInfo processInfo,
            @Nullable IPlatformCompat platformCompat) {
        // Get the desired tagging level (app manifest + compat features).
        int level = getRequestedMemtagLevel(info, processInfo, platformCompat);

        // Take into account the hardware capabilities.
        if (nativeSupportsMemoryTagging()) {
            // MTE devices can not do TBI, because the Zygote process already has live MTE
            // allocations. Downgrade TBI to NONE.
            if (level == MEMORY_TAG_LEVEL_TBI) {
                level = MEMORY_TAG_LEVEL_NONE;
            }
        } else if (nativeSupportsTaggedPointers()) {
            // TBI-but-not-MTE devices downgrade MTE modes to TBI.
            // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
            // the "fake" pointer tagging (TBI).
            if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) {
                level = MEMORY_TAG_LEVEL_TBI;
            }
        } else {
            // Otherwise disable all tagging.
            level = MEMORY_TAG_LEVEL_NONE;
        }

        // If we requested "sync" mode for the whole platform, upgrade mode for apps that enable
        // MTE.
        // This makes debugging a lot easier.
        if (level == MEMORY_TAG_LEVEL_ASYNC
                && (Build.IS_USERDEBUG || Build.IS_ENG)
                && "sync".equals(SystemProperties.get("persist.arm64.memtag.default"))) {
            level = MEMORY_TAG_LEVEL_SYNC;
        }

        return level;
    }

    private static int decideGwpAsanLevel(
            @NonNull ApplicationInfo info,
            @Nullable ProcessInfo processInfo,
            @Nullable IPlatformCompat platformCompat) {
        // Look at the process attribute first.
        if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
            return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
                    ? GWP_ASAN_LEVEL_ALWAYS
                    : GWP_ASAN_LEVEL_NEVER;
        }
        // Then at the application attribute.
        if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
            return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
                    ? GWP_ASAN_LEVEL_ALWAYS
                    : GWP_ASAN_LEVEL_NEVER;
        }
        if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
            return GWP_ASAN_LEVEL_ALWAYS;
        }
        if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            return GWP_ASAN_LEVEL_LOTTERY;
        }
        return GWP_ASAN_LEVEL_DEFAULT;
    }

    private static boolean enableNativeHeapZeroInit(
            @NonNull ApplicationInfo info,
            @Nullable ProcessInfo processInfo,
            @Nullable IPlatformCompat platformCompat) {
        // Look at the process attribute first.
        if (processInfo != null
                && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
            return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
        }
        // Then at the application attribute.
        if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
            return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
        }
        // Compat feature last.
        if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) {
            return true;
        }
        return false;
    }

    /**
     * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
     * for a given app.
     */
    public static int getMemorySafetyRuntimeFlags(
            @NonNull ApplicationInfo info,
            @Nullable ProcessInfo processInfo,
            @Nullable String instructionSet,
            @Nullable IPlatformCompat platformCompat) {
        int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat);
        // If instructionSet is non-null, this indicates that the system_server is spawning a
        // process with an ISA that may be different from its own. System (kernel and hardware)
        // compatibility for these features is checked in the decideTaggingLevel in the
        // system_server process (not the child process). As both MTE and TBI are only supported
        // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
        // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
        // enable some tagging variant. Theoretically, a 32-bit system server could exist that
        // spawns 64-bit processes, in which case the new process won't get any tagging. This is
        // fine as we haven't seen this configuration in practice, and we can reasonable assume
        // that if tagging is desired, the system server will be 64-bit.
        if (instructionSet == null || instructionSet.equals("arm64")) {
            runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat);
        }
        if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) {
            runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED;
        }
        return runtimeFlags;
    }

    /**
     * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
     * for a secondary zygote (AppZygote or WebViewZygote).
     */
    public static int getMemorySafetyRuntimeFlagsForSecondaryZygote(
            @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) {
        final IPlatformCompat platformCompat =
                IPlatformCompat.Stub.asInterface(
                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        int runtimeFlags =
                getMemorySafetyRuntimeFlags(
                        info, processInfo, null /*instructionSet*/, platformCompat);

        // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
        if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI
                && isCompatChangeEnabled(
                        NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE,
                        info,
                        platformCompat,
                        Build.VERSION_CODES.S)) {
            // Reset memory tag level to NONE.
            runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK;
            runtimeFlags |= MEMORY_TAG_LEVEL_NONE;
        }
        return runtimeFlags;
    }
}
