/*
 * Copyright (C) 2010 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.app;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
import android.content.pm.split.SplitDependencyLoader;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;

import com.android.internal.util.ArrayUtils;

import dalvik.system.VMRuntime;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

final class IntentReceiverLeaked extends AndroidRuntimeException {
    @UnsupportedAppUsage
    public IntentReceiverLeaked(String msg) {
        super(msg);
    }
}

final class ServiceConnectionLeaked extends AndroidRuntimeException {
    @UnsupportedAppUsage
    public ServiceConnectionLeaked(String msg) {
        super(msg);
    }
}

/**
 * Local state maintained about a currently loaded .apk.
 * @hide
 */
public final class LoadedApk {
    static final String TAG = "LoadedApk";
    static final boolean DEBUG = false;
    private static final String PROPERTY_NAME_APPEND_NATIVE = "pi.append_native_lib_paths";

    @UnsupportedAppUsage
    private final ActivityThread mActivityThread;
    @UnsupportedAppUsage
    final String mPackageName;
    @UnsupportedAppUsage
    private ApplicationInfo mApplicationInfo;
    @UnsupportedAppUsage
    private String mAppDir;
    @UnsupportedAppUsage
    private String mResDir;
    private String[] mOverlayDirs;
    @UnsupportedAppUsage
    private String mDataDir;
    @UnsupportedAppUsage
    private String mLibDir;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private File mDataDirFile;
    private File mDeviceProtectedDataDirFile;
    private File mCredentialProtectedDataDirFile;
    @UnsupportedAppUsage
    private final ClassLoader mBaseClassLoader;
    private ClassLoader mDefaultClassLoader;
    private final boolean mSecurityViolation;
    private final boolean mIncludeCode;
    private final boolean mRegisterPackage;
    @UnsupportedAppUsage
    private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
    /** WARNING: This may change. Don't hold external references to it. */
    @UnsupportedAppUsage
    Resources mResources;
    @UnsupportedAppUsage
    private ClassLoader mClassLoader;
    @UnsupportedAppUsage
    private Application mApplication;

    private String[] mSplitNames;
    private String[] mSplitAppDirs;
    @UnsupportedAppUsage
    private String[] mSplitResDirs;
    private String[] mSplitClassLoaderNames;

    @UnsupportedAppUsage
    private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
        = new ArrayMap<>();
    private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
        = new ArrayMap<>();
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
        = new ArrayMap<>();
    private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
        = new ArrayMap<>();
    private AppComponentFactory mAppComponentFactory;

    Application getApplication() {
        return mApplication;
    }

    /**
     * Create information about a new .apk
     *
     * NOTE: This constructor is called with ActivityThread's lock held,
     * so MUST NOT call back out to the activity manager.
     */
    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
            CompatibilityInfo compatInfo, ClassLoader baseLoader,
            boolean securityViolation, boolean includeCode, boolean registerPackage) {

        mActivityThread = activityThread;
        setApplicationInfo(aInfo);
        mPackageName = aInfo.packageName;
        mBaseClassLoader = baseLoader;
        mSecurityViolation = securityViolation;
        mIncludeCode = includeCode;
        mRegisterPackage = registerPackage;
        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
        mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader);
    }

    private static ApplicationInfo adjustNativeLibraryPaths(ApplicationInfo info) {
        // If we're dealing with a multi-arch application that has both
        // 32 and 64 bit shared libraries, we might need to choose the secondary
        // depending on what the current runtime's instruction set is.
        if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) {
            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();

            // Get the instruction set that the libraries of secondary Abi is supported.
            // In presence of a native bridge this might be different than the one secondary Abi used.
            String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi);
            final String secondaryDexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
            secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;

            // If the runtimeIsa is the same as the primary isa, then we do nothing.
            // Everything will be set up correctly because info.nativeLibraryDir will
            // correspond to the right ISA.
            if (runtimeIsa.equals(secondaryIsa)) {
                final ApplicationInfo modified = new ApplicationInfo(info);
                modified.nativeLibraryDir = modified.secondaryNativeLibraryDir;
                modified.primaryCpuAbi = modified.secondaryCpuAbi;
                return modified;
            }
        }

        return info;
    }

    /**
     * Create information about the system package.
     * Must call {@link #installSystemApplicationInfo} later.
     */
    LoadedApk(ActivityThread activityThread) {
        mActivityThread = activityThread;
        mApplicationInfo = new ApplicationInfo();
        mApplicationInfo.packageName = "android";
        mPackageName = "android";
        mAppDir = null;
        mResDir = null;
        mSplitAppDirs = null;
        mSplitResDirs = null;
        mSplitClassLoaderNames = null;
        mOverlayDirs = null;
        mDataDir = null;
        mDataDirFile = null;
        mDeviceProtectedDataDirFile = null;
        mCredentialProtectedDataDirFile = null;
        mLibDir = null;
        mBaseClassLoader = null;
        mSecurityViolation = false;
        mIncludeCode = true;
        mRegisterPackage = false;
        mResources = Resources.getSystem();
        mDefaultClassLoader = ClassLoader.getSystemClassLoader();
        mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
        mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
    }

    /**
     * Sets application info about the system package.
     */
    void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
        assert info.packageName.equals("android");
        mApplicationInfo = info;
        mDefaultClassLoader = classLoader;
        mAppComponentFactory = createAppFactory(info, mDefaultClassLoader);
        mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
    }

    private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
        if (appInfo.appComponentFactory != null && cl != null) {
            try {
                AppComponentFactory factory = (AppComponentFactory) cl.loadClass(
                        appInfo.appComponentFactory).newInstance();
                // Pass a copy of ApplicationInfo to the factory. Copying protects the framework
                // from apps which would override the factory and change ApplicationInfo contents.
                // ApplicationInfo is used to set up the default class loader.
                factory.setApplicationInfo(new ApplicationInfo(appInfo));
                return factory;
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                Slog.e(TAG, "Unable to instantiate appComponentFactory", e);
            }
        }
        return AppComponentFactory.DEFAULT;
    }

    public AppComponentFactory getAppFactory() {
        return mAppComponentFactory;
    }

    @UnsupportedAppUsage
    public String getPackageName() {
        return mPackageName;
    }

    @UnsupportedAppUsage
    public ApplicationInfo getApplicationInfo() {
        return mApplicationInfo;
    }

    public int getTargetSdkVersion() {
        return mApplicationInfo.targetSdkVersion;
    }

    public boolean isSecurityViolation() {
        return mSecurityViolation;
    }

    @UnsupportedAppUsage
    public CompatibilityInfo getCompatibilityInfo() {
        return mDisplayAdjustments.getCompatibilityInfo();
    }

    public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
    }

    /**
     * Gets the array of shared libraries that are listed as
     * used by the given package.
     *
     * @param packageName the name of the package (note: not its
     * file name)
     * @return null-ok; the array of shared libraries, each one
     * a fully-qualified path
     */
    private static String[] getLibrariesFor(String packageName) {
        ApplicationInfo ai = null;
        try {
            ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
                    PackageManager.GET_SHARED_LIBRARY_FILES, UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        if (ai == null) {
            return null;
        }

        return ai.sharedLibraryFiles;
    }

    /**
     * Update the ApplicationInfo for an app. If oldPaths is null, all the paths are considered
     * new.
     * @param aInfo The new ApplicationInfo to use for this LoadedApk
     * @param oldPaths The code paths for the old ApplicationInfo object. null means no paths can
     *                 be reused.
     */
    public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
            @Nullable List<String> oldPaths) {
        setApplicationInfo(aInfo);

        final List<String> newPaths = new ArrayList<>();
        makePaths(mActivityThread, aInfo, newPaths);
        final List<String> addedPaths = new ArrayList<>(newPaths.size());

        if (oldPaths != null) {
            for (String path : newPaths) {
                final String apkName = path.substring(path.lastIndexOf(File.separator));
                boolean match = false;
                for (String oldPath : oldPaths) {
                    final String oldApkName = oldPath.substring(oldPath.lastIndexOf(File.separator));
                    if (apkName.equals(oldApkName)) {
                        match = true;
                        break;
                    }
                }
                if (!match) {
                    addedPaths.add(path);
                }
            }
        } else {
            addedPaths.addAll(newPaths);
        }
        synchronized (this) {
            createOrUpdateClassLoaderLocked(addedPaths);
            if (mResources != null) {
                final String[] splitPaths;
                try {
                    splitPaths = getSplitPaths(null);
                } catch (NameNotFoundException e) {
                    // This should NEVER fail.
                    throw new AssertionError("null split not found");
                }

                mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                        splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                        Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                        getClassLoader());
            }
        }
        mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
    }

    private void setApplicationInfo(ApplicationInfo aInfo) {
        final int myUid = Process.myUid();
        aInfo = adjustNativeLibraryPaths(aInfo);
        mApplicationInfo = aInfo;
        mAppDir = aInfo.sourceDir;
        mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
        mOverlayDirs = aInfo.resourceDirs;
        mDataDir = aInfo.dataDir;
        mLibDir = aInfo.nativeLibraryDir;
        mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
        mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
        mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);

        mSplitNames = aInfo.splitNames;
        mSplitAppDirs = aInfo.splitSourceDirs;
        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
        mSplitClassLoaderNames = aInfo.splitClassLoaderNames;

        if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
            mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
        }
    }

    public static void makePaths(ActivityThread activityThread,
                                 ApplicationInfo aInfo,
                                 List<String> outZipPaths) {
        makePaths(activityThread, false, aInfo, outZipPaths, null);
    }

    private static void appendSharedLibrariesLibPathsIfNeeded(
            List<SharedLibraryInfo> sharedLibraries, ApplicationInfo aInfo,
            Set<String> outSeenPaths,
            List<String> outLibPaths) {
        if (sharedLibraries == null) {
            return;
        }
        for (SharedLibraryInfo lib : sharedLibraries) {
            List<String> paths = lib.getAllCodePaths();
            outSeenPaths.addAll(paths);
            for (String path : paths) {
                appendApkLibPathIfNeeded(path, aInfo, outLibPaths);
            }
            appendSharedLibrariesLibPathsIfNeeded(
                    lib.getDependencies(), aInfo, outSeenPaths, outLibPaths);
        }
    }

    public static void makePaths(ActivityThread activityThread,
                                 boolean isBundledApp,
                                 ApplicationInfo aInfo,
                                 List<String> outZipPaths,
                                 List<String> outLibPaths) {
        final String appDir = aInfo.sourceDir;
        final String libDir = aInfo.nativeLibraryDir;

        outZipPaths.clear();
        outZipPaths.add(appDir);

        // Do not load all available splits if the app requested isolated split loading.
        if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) {
            Collections.addAll(outZipPaths, aInfo.splitSourceDirs);
        }

        if (outLibPaths != null) {
            outLibPaths.clear();
        }

        /*
         * The following is a bit of a hack to inject
         * instrumentation into the system: If the app
         * being started matches one of the instrumentation names,
         * then we combine both the "instrumentation" and
         * "instrumented" app into the path, along with the
         * concatenation of both apps' shared library lists.
         */

        String[] instrumentationLibs = null;
        // activityThread will be null when called from the WebView zygote; just assume
        // no instrumentation applies in this case.
        if (activityThread != null) {
            String instrumentationPackageName = activityThread.mInstrumentationPackageName;
            String instrumentationAppDir = activityThread.mInstrumentationAppDir;
            String[] instrumentationSplitAppDirs = activityThread.mInstrumentationSplitAppDirs;
            String instrumentationLibDir = activityThread.mInstrumentationLibDir;

            String instrumentedAppDir = activityThread.mInstrumentedAppDir;
            String[] instrumentedSplitAppDirs = activityThread.mInstrumentedSplitAppDirs;
            String instrumentedLibDir = activityThread.mInstrumentedLibDir;

            if (appDir.equals(instrumentationAppDir)
                    || appDir.equals(instrumentedAppDir)) {
                outZipPaths.clear();
                outZipPaths.add(instrumentationAppDir);

                // Only add splits if the app did not request isolated split loading.
                if (!aInfo.requestsIsolatedSplitLoading()) {
                    if (instrumentationSplitAppDirs != null) {
                        Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
                    }

                    if (!instrumentationAppDir.equals(instrumentedAppDir)) {
                        outZipPaths.add(instrumentedAppDir);
                        if (instrumentedSplitAppDirs != null) {
                            Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
                        }
                    }
                }

                if (outLibPaths != null) {
                    outLibPaths.add(instrumentationLibDir);
                    if (!instrumentationLibDir.equals(instrumentedLibDir)) {
                        outLibPaths.add(instrumentedLibDir);
                    }
                }

                if (!instrumentedAppDir.equals(instrumentationAppDir)) {
                    instrumentationLibs = getLibrariesFor(instrumentationPackageName);
                }
            }
        }

        if (outLibPaths != null) {
            if (outLibPaths.isEmpty()) {
                outLibPaths.add(libDir);
            }

            // Add path to libraries in apk for current abi. Do this now because more entries
            // will be added to zipPaths that shouldn't be part of the library path.
            if (aInfo.primaryCpuAbi != null) {
                // Add fake libs into the library search path if we target prior to N.
                if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
                    outLibPaths.add("/system/fake-libs" +
                        (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
                }
                for (String apk : outZipPaths) {
                    outLibPaths.add(apk + "!/lib/" + aInfo.primaryCpuAbi);
                }
            }

            if (isBundledApp) {
                // Add path to system libraries to libPaths;
                // Access to system libs should be limited
                // to bundled applications; this is why updated
                // system apps are not included.
                outLibPaths.add(System.getProperty("java.library.path"));
            }
        }

        // Add the shared libraries native paths. The dex files in shared libraries will
        // be resolved through shared library loaders, which are setup later.
        Set<String> outSeenPaths = new LinkedHashSet<>();
        appendSharedLibrariesLibPathsIfNeeded(
                aInfo.sharedLibraryInfos, aInfo, outSeenPaths, outLibPaths);

        // ApplicationInfo.sharedLibraryFiles is a public API, so anyone can change it.
        // We prepend shared libraries that the package manager hasn't seen, maintaining their
        // original order where possible.
        if (aInfo.sharedLibraryFiles != null) {
            int index = 0;
            for (String lib : aInfo.sharedLibraryFiles) {
                if (!outSeenPaths.contains(lib) && !outZipPaths.contains(lib)) {
                    outZipPaths.add(index, lib);
                    index++;
                    appendApkLibPathIfNeeded(lib, aInfo, outLibPaths);
                }
            }
        }

        if (instrumentationLibs != null) {
            for (String lib : instrumentationLibs) {
                if (!outZipPaths.contains(lib)) {
                    outZipPaths.add(0, lib);
                    appendApkLibPathIfNeeded(lib, aInfo, outLibPaths);
                }
            }
        }
    }

    /**
     * This method appends a path to the appropriate native library folder of a
     * library if this library is hosted in an APK. This allows support for native
     * shared libraries. The library API is determined based on the application
     * ABI.
     *
     * @param path Path to the library.
     * @param applicationInfo The application depending on the library.
     * @param outLibPaths List to which to add the native lib path if needed.
     */
    private static void appendApkLibPathIfNeeded(@NonNull String path,
            @NonNull ApplicationInfo applicationInfo, @Nullable List<String> outLibPaths) {
        // Looking at the suffix is a little hacky but a safe and simple solution.
        // We will be revisiting code in the next release and clean this up.
        if (outLibPaths != null && applicationInfo.primaryCpuAbi != null && path.endsWith(".apk")) {
            if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
                outLibPaths.add(path + "!/lib/" + applicationInfo.primaryCpuAbi);
            }
        }
    }

    /*
     * All indices received by the super class should be shifted by 1 when accessing mSplitNames,
     * etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't
     * include the base APK in the list of splits.
     */
    private class SplitDependencyLoaderImpl extends SplitDependencyLoader<NameNotFoundException> {
        private final String[][] mCachedResourcePaths;
        private final ClassLoader[] mCachedClassLoaders;

        SplitDependencyLoaderImpl(@NonNull SparseArray<int[]> dependencies) {
            super(dependencies);
            mCachedResourcePaths = new String[mSplitNames.length + 1][];
            mCachedClassLoaders = new ClassLoader[mSplitNames.length + 1];
        }

        @Override
        protected boolean isSplitCached(int splitIdx) {
            return mCachedClassLoaders[splitIdx] != null;
        }

        @Override
        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
                int parentSplitIdx) throws NameNotFoundException {
            final ArrayList<String> splitPaths = new ArrayList<>();
            if (splitIdx == 0) {
                createOrUpdateClassLoaderLocked(null);
                mCachedClassLoaders[0] = mClassLoader;

                // Never add the base resources here, they always get added no matter what.
                for (int configSplitIdx : configSplitIndices) {
                    splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
                }
                mCachedResourcePaths[0] = splitPaths.toArray(new String[splitPaths.size()]);
                return;
            }

            // Since we handled the special base case above, parentSplitIdx is always valid.
            final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
            mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
                    mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent,
                    mSplitClassLoaderNames[splitIdx - 1]);

            Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
            splitPaths.add(mSplitResDirs[splitIdx - 1]);
            for (int configSplitIdx : configSplitIndices) {
                splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
            }
            mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
        }

        private int ensureSplitLoaded(String splitName) throws NameNotFoundException {
            int idx = 0;
            if (splitName != null) {
                idx = Arrays.binarySearch(mSplitNames, splitName);
                if (idx < 0) {
                    throw new PackageManager.NameNotFoundException(
                            "Split name '" + splitName + "' is not installed");
                }
                idx += 1;
            }
            loadDependenciesForSplit(idx);
            return idx;
        }

        ClassLoader getClassLoaderForSplit(String splitName) throws NameNotFoundException {
            return mCachedClassLoaders[ensureSplitLoaded(splitName)];
        }

        String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
            return mCachedResourcePaths[ensureSplitLoaded(splitName)];
        }
    }

    private SplitDependencyLoaderImpl mSplitLoader;

    ClassLoader getSplitClassLoader(String splitName) throws NameNotFoundException {
        if (mSplitLoader == null) {
            return mClassLoader;
        }
        return mSplitLoader.getClassLoaderForSplit(splitName);
    }

    String[] getSplitPaths(String splitName) throws NameNotFoundException {
        if (mSplitLoader == null) {
            return mSplitResDirs;
        }
        return mSplitLoader.getSplitPathsForSplit(splitName);
    }

    /**
     * Create a class loader for the {@code sharedLibrary}. Shared libraries are canonicalized,
     * so if we already created a class loader with that shared library, we return it.
     *
     * Implementation notes: the canonicalization of shared libraries is something dex2oat
     * also does.
     */
    ClassLoader createSharedLibraryLoader(SharedLibraryInfo sharedLibrary,
            boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
        List<String> paths = sharedLibrary.getAllCodePaths();
        List<ClassLoader> sharedLibraries = createSharedLibrariesLoaders(
                sharedLibrary.getDependencies(), isBundledApp, librarySearchPath,
                libraryPermittedPath);
        final String jars = (paths.size() == 1) ? paths.get(0) :
                TextUtils.join(File.pathSeparator, paths);

        // Shared libraries get a null parent: this has the side effect of having canonicalized
        // shared libraries using ApplicationLoaders cache, which is the behavior we want.
        return ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(jars,
                    mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, /* parent */ null,
                    /* classLoaderName */ null, sharedLibraries);
    }

    private List<ClassLoader> createSharedLibrariesLoaders(List<SharedLibraryInfo> sharedLibraries,
            boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
        if (sharedLibraries == null) {
            return null;
        }
        List<ClassLoader> loaders = new ArrayList<>();
        for (SharedLibraryInfo info : sharedLibraries) {
            loaders.add(createSharedLibraryLoader(
                    info, isBundledApp, librarySearchPath, libraryPermittedPath));
        }
        return loaders;
    }

    private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
        if (mPackageName.equals("android")) {
            // Note: This branch is taken for system server and we don't need to setup
            // jit profiling support.
            if (mClassLoader != null) {
                // nothing to update
                return;
            }

            if (mBaseClassLoader != null) {
                mDefaultClassLoader = mBaseClassLoader;
            } else {
                mDefaultClassLoader = ClassLoader.getSystemClassLoader();
            }
            mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
            mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);

            return;
        }

        // Avoid the binder call when the package is the current application package.
        // The activity manager will perform ensure that dexopt is performed before
        // spinning up the process.
        if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) {
            try {
                ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
                        PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }

        if (mRegisterPackage) {
            try {
                ActivityManager.getService().addPackageDependency(mPackageName);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        // Lists for the elements of zip/code and native libraries.
        //
        // Both lists are usually not empty. We expect on average one APK for the zip component,
        // but shared libraries and splits are not uncommon. We expect at least three elements
        // for native libraries (app-based, system, vendor). As such, give both some breathing
        // space and initialize to a small value (instead of incurring growth code).
        final List<String> zipPaths = new ArrayList<>(10);
        final List<String> libPaths = new ArrayList<>(10);

        boolean isBundledApp = mApplicationInfo.isSystemApp()
                && !mApplicationInfo.isUpdatedSystemApp();

        // Vendor apks are treated as bundled only when /vendor/lib is in the default search
        // paths. If not, they are treated as unbundled; access to system libs is limited.
        // Having /vendor/lib in the default search paths means that all system processes
        // are allowed to use any vendor library, which in turn means that system is dependent
        // on vendor partition. In the contrary, not having /vendor/lib in the default search
        // paths mean that the two partitions are separated and thus we can treat vendor apks
        // as unbundled.
        final String defaultSearchPaths = System.getProperty("java.library.path");
        final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
        if (mApplicationInfo.getCodePath() != null
                && mApplicationInfo.isVendor() && treatVendorApkAsUnbundled) {
            isBundledApp = false;
        }

        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);

        String libraryPermittedPath = mDataDir;

        if (isBundledApp) {
            // For bundled apps, add the base directory of the app (e.g.,
            // /system/app/Foo/) to the permitted paths so that it can load libraries
            // embedded in module apks under the directory. For now, GmsCore is relying
            // on this, but this isn't specific to the app. Also note that, we don't
            // need to do this for unbundled apps as entire /data is already set to
            // the permitted paths for them.
            libraryPermittedPath += File.pathSeparator
                    + Paths.get(getAppDir()).getParent().toString();

            // This is necessary to grant bundled apps access to
            // libraries located in subdirectories of /system/lib
            libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
        }

        final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

        // If we're not asked to include code, we construct a classloader that has
        // no code path included. We still need to set up the library search paths
        // and permitted path because NativeActivity relies on it (it attempts to
        // call System.loadLibrary() on a classloader from a LoadedApk with
        // mIncludeCode == false).
        if (!mIncludeCode) {
            if (mDefaultClassLoader == null) {
                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
                mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
                        "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
                        librarySearchPath, libraryPermittedPath, mBaseClassLoader,
                        null /* classLoaderName */);
                StrictMode.setThreadPolicy(oldPolicy);
                mAppComponentFactory = AppComponentFactory.DEFAULT;
            }

            if (mClassLoader == null) {
                mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
            }

            return;
        }

        /*
         * With all the combination done (if necessary, actually create the java class
         * loader and set up JIT profiling support if necessary.
         *
         * In many cases this is a single APK, so try to avoid the StringBuilder in TextUtils.
         */
        final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
                TextUtils.join(File.pathSeparator, zipPaths);

        if (DEBUG) Slog.v(ActivityThread.TAG, "Class path: " + zip +
                    ", JNI path: " + librarySearchPath);

        boolean needToSetupJitProfiles = false;
        if (mDefaultClassLoader == null) {
            // Temporarily disable logging of disk reads on the Looper thread
            // as this is early and necessary.
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();

            List<ClassLoader> sharedLibraries = createSharedLibrariesLoaders(
                    mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath,
                    libraryPermittedPath);

            mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
                    zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader,
                    mApplicationInfo.classLoaderName, sharedLibraries);
            mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);

            StrictMode.setThreadPolicy(oldPolicy);
            // Setup the class loader paths for profiling.
            needToSetupJitProfiles = true;
        }

        if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
            // Temporarily disable logging of disk reads on the Looper thread as this is necessary
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            try {
                ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, libPaths);
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }

        // /vendor/lib, /odm/lib and /product/lib are added to the native lib search
        // paths of the classloader. Note that this is done AFTER the classloader is
        // created by ApplicationLoaders.getDefault().getClassLoader(...). The
        // reason is because if we have added the paths when creating the classloader
        // above, the paths are also added to the search path of the linker namespace
        // 'classloader-namespace', which will allow ALL libs in the paths to apps.
        // Since only the libs listed in <partition>/etc/public.libraries.txt can be
        // available to apps, we shouldn't add the paths then.
        //
        // However, we need to add the paths to the classloader (Java) though. This
        // is because when a native lib is requested via System.loadLibrary(), the
        // classloader first tries to find the requested lib in its own native libs
        // search paths. If a lib is not found in one of the paths, dlopen() is not
        // called at all. This can cause a problem that a vendor public native lib
        // is accessible when directly opened via dlopen(), but inaccesible via
        // System.loadLibrary(). In order to prevent the problem, we explicitly
        // add the paths only to the classloader, and not to the native loader
        // (linker namespace).
        List<String> extraLibPaths = new ArrayList<>(3);
        String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
        if (!defaultSearchPaths.contains("/vendor/lib")) {
            extraLibPaths.add("/vendor/lib" + abiSuffix);
        }
        if (!defaultSearchPaths.contains("/odm/lib")) {
            extraLibPaths.add("/odm/lib" + abiSuffix);
        }
        if (!defaultSearchPaths.contains("/product/lib")) {
            extraLibPaths.add("/product/lib" + abiSuffix);
        }
        if (!extraLibPaths.isEmpty()) {
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            try {
                ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }

        if (addedPaths != null && addedPaths.size() > 0) {
            final String add = TextUtils.join(File.pathSeparator, addedPaths);
            ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
            // Setup the new code paths for profiling.
            needToSetupJitProfiles = true;
        }

        // Setup jit profile support.
        //
        // It is ok to call this multiple times if the application gets updated with new splits.
        // The runtime only keeps track of unique code paths and can handle re-registration of
        // the same code path. There's no need to pass `addedPaths` since any new code paths
        // are already in `mApplicationInfo`.
        //
        // It is NOT ok to call this function from the system_server (for any of the packages it
        // loads code from) so we explicitly disallow it there.
        if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
            setupJitProfileSupport();
        }

        // Call AppComponentFactory to select/create the main class loader of this app.
        // Since this may call code in the app, mDefaultClassLoader must be fully set up
        // before invoking the factory.
        if (mClassLoader == null) {
            mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
        }
    }

    @UnsupportedAppUsage
    public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader == null) {
                createOrUpdateClassLoaderLocked(null /*addedPaths*/);
            }
            return mClassLoader;
        }
    }

    private void setupJitProfileSupport() {
        if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
            return;
        }
        // Only set up profile support if the loaded apk has the same uid as the
        // current process.
        // Currently, we do not support profiling across different apps.
        // (e.g. application's uid might be different when the code is
        // loaded by another app via createApplicationContext)
        if (mApplicationInfo.uid != Process.myUid()) {
            return;
        }

        final List<String> codePaths = new ArrayList<>();
        if ((mApplicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
            codePaths.add(mApplicationInfo.sourceDir);
        }
        if (mApplicationInfo.splitSourceDirs != null) {
            Collections.addAll(codePaths, mApplicationInfo.splitSourceDirs);
        }

        if (codePaths.isEmpty()) {
            // If there are no code paths there's no need to setup a profile file and register with
            // the runtime,
            return;
        }

        for (int i = codePaths.size() - 1; i >= 0; i--) {
            String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
            String profileFile = ArtManager.getCurrentProfilePath(
                    mPackageName, UserHandle.myUserId(), splitName);
            VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
        }

        // Register the app data directory with the reporter. It will
        // help deciding whether or not a dex file is the primary apk or a
        // secondary dex.
        DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir);
    }

    /**
     * Setup value for Thread.getContextClassLoader(). If the
     * package will not run in in a VM with other packages, we set
     * the Java context ClassLoader to the
     * PackageInfo.getClassLoader value. However, if this VM can
     * contain multiple packages, we intead set the Java context
     * ClassLoader to a proxy that will warn about the use of Java
     * context ClassLoaders and then fall through to use the
     * system ClassLoader.
     *
     * <p> Note that this is similar to but not the same as the
     * android.content.Context.getClassLoader(). While both
     * context class loaders are typically set to the
     * PathClassLoader used to load the package archive in the
     * single application per VM case, a single Android process
     * may contain several Contexts executing on one thread with
     * their own logical ClassLoaders while the Java context
     * ClassLoader is a thread local. This is why in the case when
     * we have multiple packages per VM we do not set the Java
     * context ClassLoader to an arbitrary but instead warn the
     * user to set their own if we detect that they are using a
     * Java library that expects it to be set.
     */
    private void initializeJavaContextClassLoader() {
        IPackageManager pm = ActivityThread.getPackageManager();
        android.content.pm.PackageInfo pi;
        try {
            pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        if (pi == null) {
            throw new IllegalStateException("Unable to get package info for "
                    + mPackageName + "; is package not installed?");
        }
        /*
         * Two possible indications that this package could be
         * sharing its virtual machine with other packages:
         *
         * 1.) the sharedUserId attribute is set in the manifest,
         *     indicating a request to share a VM with other
         *     packages with the same sharedUserId.
         *
         * 2.) the application element of the manifest has an
         *     attribute specifying a non-default process name,
         *     indicating the desire to run in another packages VM.
         */
        boolean sharedUserIdSet = (pi.sharedUserId != null);
        boolean processNameNotDefault =
            (pi.applicationInfo != null &&
             !mPackageName.equals(pi.applicationInfo.processName));
        boolean sharable = (sharedUserIdSet || processNameNotDefault);
        ClassLoader contextClassLoader =
            (sharable)
            ? new WarningContextClassLoader()
            : mClassLoader;
        Thread.currentThread().setContextClassLoader(contextClassLoader);
    }

    private static class WarningContextClassLoader extends ClassLoader {

        private static boolean warned = false;

        private void warn(String methodName) {
            if (warned) {
                return;
            }
            warned = true;
            Thread.currentThread().setContextClassLoader(getParent());
            Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " +
                  "The class loader returned by " +
                  "Thread.getContextClassLoader() may fail for processes " +
                  "that host multiple applications. You should explicitly " +
                  "specify a context class loader. For example: " +
                  "Thread.setContextClassLoader(getClass().getClassLoader());");
        }

        @Override public URL getResource(String resName) {
            warn("getResource");
            return getParent().getResource(resName);
        }

        @Override public Enumeration<URL> getResources(String resName) throws IOException {
            warn("getResources");
            return getParent().getResources(resName);
        }

        @Override public InputStream getResourceAsStream(String resName) {
            warn("getResourceAsStream");
            return getParent().getResourceAsStream(resName);
        }

        @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
            warn("loadClass");
            return getParent().loadClass(className);
        }

        @Override public void setClassAssertionStatus(String cname, boolean enable) {
            warn("setClassAssertionStatus");
            getParent().setClassAssertionStatus(cname, enable);
        }

        @Override public void setPackageAssertionStatus(String pname, boolean enable) {
            warn("setPackageAssertionStatus");
            getParent().setPackageAssertionStatus(pname, enable);
        }

        @Override public void setDefaultAssertionStatus(boolean enable) {
            warn("setDefaultAssertionStatus");
            getParent().setDefaultAssertionStatus(enable);
        }

        @Override public void clearAssertionStatus() {
            warn("clearAssertionStatus");
            getParent().clearAssertionStatus();
        }
    }

    @UnsupportedAppUsage
    public String getAppDir() {
        return mAppDir;
    }

    public String getLibDir() {
        return mLibDir;
    }

    @UnsupportedAppUsage
    public String getResDir() {
        return mResDir;
    }

    public String[] getSplitAppDirs() {
        return mSplitAppDirs;
    }

    @UnsupportedAppUsage
    public String[] getSplitResDirs() {
        return mSplitResDirs;
    }

    @UnsupportedAppUsage
    public String[] getOverlayDirs() {
        return mOverlayDirs;
    }

    public String getDataDir() {
        return mDataDir;
    }

    @UnsupportedAppUsage
    public File getDataDirFile() {
        return mDataDirFile;
    }

    public File getDeviceProtectedDataDirFile() {
        return mDeviceProtectedDataDirFile;
    }

    public File getCredentialProtectedDataDirFile() {
        return mCredentialProtectedDataDirFile;
    }

    @UnsupportedAppUsage
    public AssetManager getAssets() {
        return getResources().getAssets();
    }

    @UnsupportedAppUsage
    public Resources getResources() {
        if (mResources == null) {
            final String[] splitPaths;
            try {
                splitPaths = getSplitPaths(null);
            } catch (NameNotFoundException e) {
                // This should never fail.
                throw new AssertionError("null split not found");
            }

            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                    getClassLoader());
        }
        return mResources;
    }

    @UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader");
                initializeJavaContextClassLoader();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            if (!mActivityThread.mInstrumentation.onException(app, e)) {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                    + ": " + e.toString(), e);
            }
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

        if (instrumentation != null) {
            try {
                instrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!instrumentation.onException(app, e)) {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        }

        // Rewrite the R 'constants' for all library apks.
        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
        final int N = packageIdentifiers.size();
        for (int i = 0; i < N; i++) {
            final int id = packageIdentifiers.keyAt(i);
            if (id == 0x01 || id == 0x7f) {
                continue;
            }

            rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        return app;
    }

    @UnsupportedAppUsage
    private void rewriteRValues(ClassLoader cl, String packageName, int id) {
        final Class<?> rClazz;
        try {
            rClazz = cl.loadClass(packageName + ".R");
        } catch (ClassNotFoundException e) {
            // This is not necessarily an error, as some packages do not ship with resources
            // (or they do not need rewriting).
            Log.i(TAG, "No resource references to update in package " + packageName);
            return;
        }

        final Method callback;
        try {
            callback = rClazz.getMethod("onResourcesLoaded", int.class);
        } catch (NoSuchMethodException e) {
            // No rewriting to be done.
            return;
        }

        Throwable cause;
        try {
            callback.invoke(null, id);
            return;
        } catch (IllegalAccessException e) {
            cause = e;
        } catch (InvocationTargetException e) {
            cause = e.getCause();
        }

        throw new RuntimeException("Failed to rewrite resource references for " + packageName,
                cause);
    }

    public void removeContextRegistrations(Context context,
            String who, String what) {
        final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled();
        synchronized (mReceivers) {
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
                    mReceivers.remove(context);
            if (rmap != null) {
                for (int i = 0; i < rmap.size(); i++) {
                    LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i);
                    IntentReceiverLeaked leak = new IntentReceiverLeaked(
                            what + " " + who + " has leaked IntentReceiver "
                            + rd.getIntentReceiver() + " that was " +
                            "originally registered here. Are you missing a " +
                            "call to unregisterReceiver()?");
                    leak.setStackTrace(rd.getLocation().getStackTrace());
                    Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
                    if (reportRegistrationLeaks) {
                        StrictMode.onIntentReceiverLeaked(leak);
                    }
                    try {
                        ActivityManager.getService().unregisterReceiver(
                                rd.getIIntentReceiver());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
            mUnregisteredReceivers.remove(context);
        }

        synchronized (mServices) {
            //Slog.i(TAG, "Receiver registrations: " + mReceivers);
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
                    mServices.remove(context);
            if (smap != null) {
                for (int i = 0; i < smap.size(); i++) {
                    LoadedApk.ServiceDispatcher sd = smap.valueAt(i);
                    ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
                            what + " " + who + " has leaked ServiceConnection "
                            + sd.getServiceConnection() + " that was originally bound here");
                    leak.setStackTrace(sd.getLocation().getStackTrace());
                    Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
                    if (reportRegistrationLeaks) {
                        StrictMode.onServiceConnectionLeaked(leak);
                    }
                    try {
                        ActivityManager.getService().unbindService(
                                sd.getIServiceConnection());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    sd.doForget();
                }
            }
            mUnboundServices.remove(context);
            //Slog.i(TAG, "Service registrations: " + mServices);
        }
    }

    public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
            Context context, Handler handler,
            Instrumentation instrumentation, boolean registered) {
        synchronized (mReceivers) {
            LoadedApk.ReceiverDispatcher rd = null;
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
            if (registered) {
                map = mReceivers.get(context);
                if (map != null) {
                    rd = map.get(r);
                }
            }
            if (rd == null) {
                rd = new ReceiverDispatcher(r, context, handler,
                        instrumentation, registered);
                if (registered) {
                    if (map == null) {
                        map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                        mReceivers.put(context, map);
                    }
                    map.put(r, rd);
                }
            } else {
                rd.validate(context, handler);
            }
            rd.mForgotten = false;
            return rd.getIIntentReceiver();
        }
    }

    public IIntentReceiver forgetReceiverDispatcher(Context context,
            BroadcastReceiver r) {
        synchronized (mReceivers) {
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context);
            LoadedApk.ReceiverDispatcher rd = null;
            if (map != null) {
                rd = map.get(r);
                if (rd != null) {
                    map.remove(r);
                    if (map.size() == 0) {
                        mReceivers.remove(context);
                    }
                    if (r.getDebugUnregister()) {
                        ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
                                = mUnregisteredReceivers.get(context);
                        if (holder == null) {
                            holder = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                            mUnregisteredReceivers.put(context, holder);
                        }
                        RuntimeException ex = new IllegalArgumentException(
                                "Originally unregistered here:");
                        ex.fillInStackTrace();
                        rd.setUnregisterLocation(ex);
                        holder.put(r, rd);
                    }
                    rd.mForgotten = true;
                    return rd.getIIntentReceiver();
                }
            }
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
                    = mUnregisteredReceivers.get(context);
            if (holder != null) {
                rd = holder.get(r);
                if (rd != null) {
                    RuntimeException ex = rd.getUnregisterLocation();
                    throw new IllegalArgumentException(
                            "Unregistering Receiver " + r
                            + " that was already unregistered", ex);
                }
            }
            if (context == null) {
                throw new IllegalStateException("Unbinding Receiver " + r
                        + " from Context that is no longer in use: " + context);
            } else {
                throw new IllegalArgumentException("Receiver not registered: " + r);
            }

        }
    }

    static final class ReceiverDispatcher {

        final static class InnerReceiver extends IIntentReceiver.Stub {
            final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
            final LoadedApk.ReceiverDispatcher mStrongRef;

            InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
                mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
                mStrongRef = strong ? rd : null;
            }

            @Override
            public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                final LoadedApk.ReceiverDispatcher rd;
                if (intent == null) {
                    Log.wtf(TAG, "Null intent received");
                    rd = null;
                } else {
                    rd = mDispatcher.get();
                }
                if (ActivityThread.DEBUG_BROADCAST) {
                    int seq = intent.getIntExtra("seq", -1);
                    Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction()
                            + " seq=" + seq + " to " + (rd != null ? rd.mReceiver : null));
                }
                if (rd != null) {
                    rd.performReceive(intent, resultCode, data, extras,
                            ordered, sticky, sendingUser);
                } else {
                    // The activity manager dispatched a broadcast to a registered
                    // receiver in this process, but before it could be delivered the
                    // receiver was unregistered.  Acknowledge the broadcast on its
                    // behalf so that the system's broadcast sequence can continue.
                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                            "Finishing broadcast to unregistered receiver");
                    IActivityManager mgr = ActivityManager.getService();
                    try {
                        if (extras != null) {
                            extras.setAllowFds(false);
                        }
                        mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        }

        final IIntentReceiver.Stub mIIntentReceiver;
        @UnsupportedAppUsage
        final BroadcastReceiver mReceiver;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        final Context mContext;
        final Handler mActivityThread;
        final Instrumentation mInstrumentation;
        final boolean mRegistered;
        final IntentReceiverLeaked mLocation;
        RuntimeException mUnregisterLocation;
        boolean mForgotten;

        final class Args extends BroadcastReceiver.PendingResult {
            private Intent mCurIntent;
            private final boolean mOrdered;
            private boolean mDispatched;
            private Throwable mPreviousRunStacktrace; // To investigate b/37809561. STOPSHIP remove.

            public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
                    boolean ordered, boolean sticky, int sendingUser) {
                super(resultCode, resultData, resultExtras,
                        mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
                        sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags());
                mCurIntent = intent;
                mOrdered = ordered;
            }

            public final Runnable getRunnable() {
                return () -> {
                    final BroadcastReceiver receiver = mReceiver;
                    final boolean ordered = mOrdered;

                    if (ActivityThread.DEBUG_BROADCAST) {
                        int seq = mCurIntent.getIntExtra("seq", -1);
                        Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
                                + " seq=" + seq + " to " + mReceiver);
                        Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
                                + " mOrderedHint=" + ordered);
                    }

                    final IActivityManager mgr = ActivityManager.getService();
                    final Intent intent = mCurIntent;
                    if (intent == null) {
                        Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched
                                + ": run() previously called at "
                                + Log.getStackTraceString(mPreviousRunStacktrace));
                    }

                    mCurIntent = null;
                    mDispatched = true;
                    mPreviousRunStacktrace = new Throwable("Previous stacktrace");
                    if (receiver == null || intent == null || mForgotten) {
                        if (mRegistered && ordered) {
                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                    "Finishing null broadcast to " + mReceiver);
                            sendFinished(mgr);
                        }
                        return;
                    }

                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
                    try {
                        ClassLoader cl = mReceiver.getClass().getClassLoader();
                        intent.setExtrasClassLoader(cl);
                        intent.prepareToEnterProcess();
                        setExtrasClassLoader(cl);
                        receiver.setPendingResult(this);
                        receiver.onReceive(mContext, intent);
                    } catch (Exception e) {
                        if (mRegistered && ordered) {
                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                    "Finishing failed broadcast to " + mReceiver);
                            sendFinished(mgr);
                        }
                        if (mInstrumentation == null ||
                                !mInstrumentation.onException(mReceiver, e)) {
                            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                            throw new RuntimeException(
                                    "Error receiving broadcast " + intent
                                            + " in " + mReceiver, e);
                        }
                    }

                    if (receiver.getPendingResult() != null) {
                        finish();
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                };
            }
        }

        ReceiverDispatcher(BroadcastReceiver receiver, Context context,
                Handler activityThread, Instrumentation instrumentation,
                boolean registered) {
            if (activityThread == null) {
                throw new NullPointerException("Handler must not be null");
            }

            mIIntentReceiver = new InnerReceiver(this, !registered);
            mReceiver = receiver;
            mContext = context;
            mActivityThread = activityThread;
            mInstrumentation = instrumentation;
            mRegistered = registered;
            mLocation = new IntentReceiverLeaked(null);
            mLocation.fillInStackTrace();
        }

        void validate(Context context, Handler activityThread) {
            if (mContext != context) {
                throw new IllegalStateException(
                    "Receiver " + mReceiver +
                    " registered with differing Context (was " +
                    mContext + " now " + context + ")");
            }
            if (mActivityThread != activityThread) {
                throw new IllegalStateException(
                    "Receiver " + mReceiver +
                    " registered with differing handler (was " +
                    mActivityThread + " now " + activityThread + ")");
            }
        }

        IntentReceiverLeaked getLocation() {
            return mLocation;
        }

        @UnsupportedAppUsage
        BroadcastReceiver getIntentReceiver() {
            return mReceiver;
        }

        @UnsupportedAppUsage
        IIntentReceiver getIIntentReceiver() {
            return mIIntentReceiver;
        }

        void setUnregisterLocation(RuntimeException ex) {
            mUnregisterLocation = ex;
        }

        RuntimeException getUnregisterLocation() {
            return mUnregisterLocation;
        }

        public void performReceive(Intent intent, int resultCode, String data,
                Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
            final Args args = new Args(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
            if (intent == null) {
                Log.wtf(TAG, "Null intent received");
            } else {
                if (ActivityThread.DEBUG_BROADCAST) {
                    int seq = intent.getIntExtra("seq", -1);
                    Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction()
                            + " seq=" + seq + " to " + mReceiver);
                }
            }
            if (intent == null || !mActivityThread.post(args.getRunnable())) {
                if (mRegistered && ordered) {
                    IActivityManager mgr = ActivityManager.getService();
                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                            "Finishing sync broadcast to " + mReceiver);
                    args.sendFinished(mgr);
                }
            }
        }

    }

    @UnsupportedAppUsage
    public final IServiceConnection getServiceDispatcher(ServiceConnection c,
            Context context, Handler handler, int flags) {
        synchronized (mServices) {
            LoadedApk.ServiceDispatcher sd = null;
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
            if (map != null) {
                if (DEBUG) Slog.d(TAG, "Returning existing dispatcher " + sd + " for conn " + c);
                sd = map.get(c);
            }
            if (sd == null) {
                sd = new ServiceDispatcher(c, context, handler, flags);
                if (DEBUG) Slog.d(TAG, "Creating new dispatcher " + sd + " for conn " + c);
                if (map == null) {
                    map = new ArrayMap<>();
                    mServices.put(context, map);
                }
                map.put(c, sd);
            } else {
                sd.validate(context, handler);
            }
            return sd.getIServiceConnection();
        }
    }

    public final IServiceConnection forgetServiceDispatcher(Context context,
            ServiceConnection c) {
        synchronized (mServices) {
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map
                    = mServices.get(context);
            LoadedApk.ServiceDispatcher sd = null;
            if (map != null) {
                sd = map.get(c);
                if (sd != null) {
                    if (DEBUG) Slog.d(TAG, "Removing dispatcher " + sd + " for conn " + c);
                    map.remove(c);
                    sd.doForget();
                    if (map.size() == 0) {
                        mServices.remove(context);
                    }
                    if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
                        ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
                                = mUnboundServices.get(context);
                        if (holder == null) {
                            holder = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                            mUnboundServices.put(context, holder);
                        }
                        RuntimeException ex = new IllegalArgumentException(
                                "Originally unbound here:");
                        ex.fillInStackTrace();
                        sd.setUnbindLocation(ex);
                        holder.put(c, sd);
                    }
                    return sd.getIServiceConnection();
                }
            }
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
                    = mUnboundServices.get(context);
            if (holder != null) {
                sd = holder.get(c);
                if (sd != null) {
                    RuntimeException ex = sd.getUnbindLocation();
                    throw new IllegalArgumentException(
                            "Unbinding Service " + c
                            + " that was already unbound", ex);
                }
            }
            if (context == null) {
                throw new IllegalStateException("Unbinding Service " + c
                        + " from Context that is no longer in use: " + context);
            } else {
                throw new IllegalArgumentException("Service not registered: " + c);
            }
        }
    }

    static final class ServiceDispatcher {
        private final ServiceDispatcher.InnerConnection mIServiceConnection;
        @UnsupportedAppUsage
        private final ServiceConnection mConnection;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        private final Context mContext;
        private final Handler mActivityThread;
        private final ServiceConnectionLeaked mLocation;
        private final int mFlags;

        private RuntimeException mUnbindLocation;

        private boolean mForgotten;

        private static class ConnectionInfo {
            IBinder binder;
            IBinder.DeathRecipient deathMonitor;
        }

        private static class InnerConnection extends IServiceConnection.Stub {
            @UnsupportedAppUsage
            final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

            InnerConnection(LoadedApk.ServiceDispatcher sd) {
                mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
            }

            public void connected(ComponentName name, IBinder service, boolean dead)
                    throws RemoteException {
                LoadedApk.ServiceDispatcher sd = mDispatcher.get();
                if (sd != null) {
                    sd.connected(name, service, dead);
                }
            }
        }

        private final ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
            = new ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo>();

        @UnsupportedAppUsage
        ServiceDispatcher(ServiceConnection conn,
                Context context, Handler activityThread, int flags) {
            mIServiceConnection = new InnerConnection(this);
            mConnection = conn;
            mContext = context;
            mActivityThread = activityThread;
            mLocation = new ServiceConnectionLeaked(null);
            mLocation.fillInStackTrace();
            mFlags = flags;
        }

        void validate(Context context, Handler activityThread) {
            if (mContext != context) {
                throw new RuntimeException(
                    "ServiceConnection " + mConnection +
                    " registered with differing Context (was " +
                    mContext + " now " + context + ")");
            }
            if (mActivityThread != activityThread) {
                throw new RuntimeException(
                    "ServiceConnection " + mConnection +
                    " registered with differing handler (was " +
                    mActivityThread + " now " + activityThread + ")");
            }
        }

        void doForget() {
            synchronized(this) {
                for (int i=0; i<mActiveConnections.size(); i++) {
                    ServiceDispatcher.ConnectionInfo ci = mActiveConnections.valueAt(i);
                    ci.binder.unlinkToDeath(ci.deathMonitor, 0);
                }
                mActiveConnections.clear();
                mForgotten = true;
            }
        }

        ServiceConnectionLeaked getLocation() {
            return mLocation;
        }

        ServiceConnection getServiceConnection() {
            return mConnection;
        }

        @UnsupportedAppUsage
        IServiceConnection getIServiceConnection() {
            return mIServiceConnection;
        }

        int getFlags() {
            return mFlags;
        }

        void setUnbindLocation(RuntimeException ex) {
            mUnbindLocation = ex;
        }

        RuntimeException getUnbindLocation() {
            return mUnbindLocation;
        }

        public void connected(ComponentName name, IBinder service, boolean dead) {
            if (mActivityThread != null) {
                mActivityThread.post(new RunConnection(name, service, 0, dead));
            } else {
                doConnected(name, service, dead);
            }
        }

        public void death(ComponentName name, IBinder service) {
            if (mActivityThread != null) {
                mActivityThread.post(new RunConnection(name, service, 1, false));
            } else {
                doDeath(name, service);
            }
        }

        public void doConnected(ComponentName name, IBinder service, boolean dead) {
            ServiceDispatcher.ConnectionInfo old;
            ServiceDispatcher.ConnectionInfo info;

            synchronized (this) {
                if (mForgotten) {
                    // We unbound before receiving the connection; ignore
                    // any connection received.
                    return;
                }
                old = mActiveConnections.get(name);
                if (old != null && old.binder == service) {
                    // Huh, already have this one.  Oh well!
                    return;
                }

                if (service != null) {
                    // A new service is being connected... set it all up.
                    info = new ConnectionInfo();
                    info.binder = service;
                    info.deathMonitor = new DeathMonitor(name, service);
                    try {
                        service.linkToDeath(info.deathMonitor, 0);
                        mActiveConnections.put(name, info);
                    } catch (RemoteException e) {
                        // This service was dead before we got it...  just
                        // don't do anything with it.
                        mActiveConnections.remove(name);
                        return;
                    }

                } else {
                    // The named service is being disconnected... clean up.
                    mActiveConnections.remove(name);
                }

                if (old != null) {
                    old.binder.unlinkToDeath(old.deathMonitor, 0);
                }
            }

            // If there was an old service, it is now disconnected.
            if (old != null) {
                mConnection.onServiceDisconnected(name);
            }
            if (dead) {
                mConnection.onBindingDied(name);
            }
            // If there is a new viable service, it is now connected.
            if (service != null) {
                mConnection.onServiceConnected(name, service);
            } else {
                // The binding machinery worked, but the remote returned null from onBind().
                mConnection.onNullBinding(name);
            }
        }

        public void doDeath(ComponentName name, IBinder service) {
            synchronized (this) {
                ConnectionInfo old = mActiveConnections.get(name);
                if (old == null || old.binder != service) {
                    // Death for someone different than who we last
                    // reported...  just ignore it.
                    return;
                }
                mActiveConnections.remove(name);
                old.binder.unlinkToDeath(old.deathMonitor, 0);
            }

            mConnection.onServiceDisconnected(name);
        }

        private final class RunConnection implements Runnable {
            RunConnection(ComponentName name, IBinder service, int command, boolean dead) {
                mName = name;
                mService = service;
                mCommand = command;
                mDead = dead;
            }

            public void run() {
                if (mCommand == 0) {
                    doConnected(mName, mService, mDead);
                } else if (mCommand == 1) {
                    doDeath(mName, mService);
                }
            }

            final ComponentName mName;
            final IBinder mService;
            final int mCommand;
            final boolean mDead;
        }

        private final class DeathMonitor implements IBinder.DeathRecipient
        {
            DeathMonitor(ComponentName name, IBinder service) {
                mName = name;
                mService = service;
            }

            public void binderDied() {
                death(mName, mService);
            }

            final ComponentName mName;
            final IBinder mService;
        }
    }
}
